/*
 * Copyright (C) 2014 Patrick Mours
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "effect_lexer.hpp"
#include "effect_parser.hpp"
#include "effect_codegen.hpp"
#include <cassert>

#define RESHADEFX_SHORT_CIRCUIT 0

reshadefx::parser::parser()
{
}
reshadefx::parser::~parser()
{
}

void reshadefx::parser::error(const location &location, unsigned int code, const std::string &message)
{
	_errors += location.source;
	_errors += '(' + std::to_string(location.line) + ", " + std::to_string(location.column) + ')' + ": error";
	_errors += (code == 0) ? ": " : " X" + std::to_string(code) + ": ";
	_errors += message;
	_errors += '\n';
}
void reshadefx::parser::warning(const location &location, unsigned int code, const std::string &message)
{
	_errors += location.source;
	_errors += '(' + std::to_string(location.line) + ", " + std::to_string(location.column) + ')' + ": warning";
	_errors += (code == 0) ? ": " : " X" + std::to_string(code) + ": ";
	_errors += message;
	_errors += '\n';
}

void reshadefx::parser::backup()
{
	_token_backup = _token_next;
	_lexer_backup_offset = _lexer->input_offset();
}
void reshadefx::parser::restore()
{
	_lexer->reset_to_offset(_lexer_backup_offset);
	_token_next = _token_backup; // Copy instead of move here, since restore may be called twice (from 'accept_type_class' and then again from 'parse_expression_unary')
}

void reshadefx::parser::consume()
{
	_token = std::move(_token_next);
	_token_next = _lexer->lex();
}
void reshadefx::parser::consume_until(tokenid tokid)
{
	while (!accept(tokid) && !peek(tokenid::end_of_file))
	{
		consume();
	}
}

bool reshadefx::parser::accept(tokenid tokid)
{
	if (peek(tokid))
	{
		consume();
		return true;
	}

	return false;
}
bool reshadefx::parser::expect(tokenid tokid)
{
	if (!accept(tokid))
	{
		error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected '" + token::id_to_name(tokid) + '\'');
		return false;
	}

	return true;
}

bool reshadefx::parser::accept_symbol(std::string &identifier, scoped_symbol &symbol)
{
	// Starting an identifier with '::' restricts the symbol search to the global namespace level
	const bool exclusive = accept(tokenid::colon_colon);

	if (exclusive ? !expect(tokenid::identifier) : !accept(tokenid::identifier))
	{
		// No token should come through here, since all possible prefix expressions should have been handled above, so this is an error in the syntax
		if (!exclusive)
			error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + '\'');
		return false;
	}

	identifier = std::move(_token.literal_as_string);

	// Can concatenate multiple '::' to force symbol search for a specific namespace level
	while (accept(tokenid::colon_colon))
	{
		if (!expect(tokenid::identifier))
			return false;
		identifier += "::" + std::move(_token.literal_as_string);
	}

	// Figure out which scope to start searching in
	scope scope = { "::", 0, 0 };
	if (!exclusive)
		scope = current_scope();

	// Lookup name in the symbol table
	symbol = find_symbol(identifier, scope, exclusive);

	return true;
}
bool reshadefx::parser::accept_type_class(type &type)
{
	type.rows = type.cols = 0;

	if (peek(tokenid::identifier) || peek(tokenid::colon_colon))
	{
		type.base = type::t_struct;

		backup(); // Need to restore if this identifier does not turn out to be a structure

		std::string identifier;
		scoped_symbol symbol;
		if (accept_symbol(identifier, symbol))
		{
			if (symbol.id && symbol.op == symbol_type::structure)
			{
				type.definition = symbol.id;
				return true;
			}
		}

		restore();

		return false;
	}

	if (accept(tokenid::vector))
	{
		type.base = type::t_float; // Default to float4 unless a type is specified (see below)
		type.rows = 4, type.cols = 1;

		if (accept('<'))
		{
			if (!accept_type_class(type)) // This overwrites the base type again
				return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected vector element type"), false;
			else if (!type.is_scalar())
				return error(_token.location, 3122, "vector element type must be a scalar type"), false;

			if (!expect(',') || !expect(tokenid::int_literal))
				return false;
			else if (_token.literal_as_int < 1 || _token.literal_as_int > 4)
				return error(_token.location, 3052, "vector dimension must be between 1 and 4"), false;

			type.rows = static_cast<unsigned int>(_token.literal_as_int);

			if (!expect('>'))
				return false;
		}

		return true;
	}
	if (accept(tokenid::matrix))
	{
		type.base = type::t_float; // Default to float4x4 unless a type is specified (see below)
		type.rows = 4, type.cols = 4;

		if (accept('<'))
		{
			if (!accept_type_class(type)) // This overwrites the base type again
				return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected matrix element type"), false;
			else if (!type.is_scalar())
				return error(_token.location, 3123, "matrix element type must be a scalar type"), false;

			if (!expect(',') || !expect(tokenid::int_literal))
				return false;
			else if (_token.literal_as_int < 1 || _token.literal_as_int > 4)
				return error(_token.location, 3053, "matrix dimensions must be between 1 and 4"), false;

			type.rows = static_cast<unsigned int>(_token.literal_as_int);

			if (!expect(',') || !expect(tokenid::int_literal))
				return false;
			else if (_token.literal_as_int < 1 || _token.literal_as_int > 4)
				return error(_token.location, 3053, "matrix dimensions must be between 1 and 4"), false;

			type.cols = static_cast<unsigned int>(_token.literal_as_int);

			if (!expect('>'))
				return false;
		}

		return true;
	}

	if (accept(tokenid::sampler1d) || accept(tokenid::sampler2d) || accept(tokenid::sampler3d))
	{
		const unsigned int texture_dimension = static_cast<unsigned int>(_token.id) - static_cast<unsigned int>(tokenid::sampler1d);

		if (accept('<'))
		{
			if (!accept_type_class(type))
				return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected sampler element type"), false;
			if (type.is_object())
				return error(_token.location, 3124, "object element type cannot be an object type"), false;
			if (!type.is_numeric() || type.is_matrix())
				return error(_token.location, 3521, "sampler element type must fit in four 32-bit quantities"), false;

			if (type.is_integral() && type.is_signed())
				type.base = static_cast<type::datatype>(type::t_sampler1d_int + texture_dimension);
			else if (type.is_integral() && type.is_unsigned())
				type.base = static_cast<type::datatype>(type::t_sampler1d_uint + texture_dimension);
			else
				type.base = static_cast<type::datatype>(type::t_sampler1d_float + texture_dimension);

			if (!expect('>'))
				return false;
		}
		else
		{
			type.base = static_cast<type::datatype>(type::t_sampler1d_float + texture_dimension);
			type.rows = 4;
			type.cols = 1;
		}

		return true;
	}
	if (accept(tokenid::storage1d) || accept(tokenid::storage2d) || accept(tokenid::storage3d))
	{
		const unsigned int texture_dimension = static_cast<unsigned int>(_token.id) - static_cast<unsigned int>(tokenid::storage1d);

		if (accept('<'))
		{
			if (!accept_type_class(type))
				return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected storage element type"), false;
			if (type.is_object())
				return error(_token.location, 3124, "object element type cannot be an object type"), false;
			if (!type.is_numeric() || type.is_matrix())
				return error(_token.location, 3521, "storage element type must fit in four 32-bit quantities"), false;

			if (type.is_integral() && type.is_signed())
				type.base = static_cast<type::datatype>(type::t_storage1d_int + texture_dimension);
			else if (type.is_integral() && type.is_unsigned())
				type.base = static_cast<type::datatype>(type::t_storage1d_uint + texture_dimension);
			else
				type.base = static_cast<type::datatype>(type::t_storage1d_float + texture_dimension);

			if (!expect('>'))
				return false;
		}
		else
		{
			type.base = static_cast<type::datatype>(type::t_storage1d_float + texture_dimension);
			type.rows = 4;
			type.cols = 1;
		}

		return true;
	}

	switch (_token_next.id)
	{
	case tokenid::void_:
		type.base = type::t_void;
		break;
	case tokenid::bool_:
	case tokenid::bool2:
	case tokenid::bool3:
	case tokenid::bool4:
		type.base = type::t_bool;
		type.rows = 1 + (static_cast<unsigned int>(_token_next.id) - static_cast<unsigned int>(tokenid::bool_));
		type.cols = 1;
		break;
	case tokenid::bool2x2:
	case tokenid::bool2x3:
	case tokenid::bool2x4:
	case tokenid::bool3x2:
	case tokenid::bool3x3:
	case tokenid::bool3x4:
	case tokenid::bool4x2:
	case tokenid::bool4x3:
	case tokenid::bool4x4:
		type.base = type::t_bool;
		type.rows = 2 + (static_cast<unsigned int>(_token_next.id) - static_cast<unsigned int>(tokenid::bool2x2)) / 3;
		type.cols = 2 + (static_cast<unsigned int>(_token_next.id) - static_cast<unsigned int>(tokenid::bool2x2)) % 3;
		break;
	case tokenid::int_:
	case tokenid::int2:
	case tokenid::int3:
	case tokenid::int4:
		type.base = type::t_int;
		type.rows = 1 + (static_cast<unsigned int>(_token_next.id) - static_cast<unsigned int>(tokenid::int_));
		type.cols = 1;
		break;
	case tokenid::int2x2:
	case tokenid::int2x3:
	case tokenid::int2x4:
	case tokenid::int3x2:
	case tokenid::int3x3:
	case tokenid::int3x4:
	case tokenid::int4x2:
	case tokenid::int4x3:
	case tokenid::int4x4:
		type.base = type::t_int;
		type.rows = 2 + (static_cast<unsigned int>(_token_next.id) - static_cast<unsigned int>(tokenid::int2x2)) / 3;
		type.cols = 2 + (static_cast<unsigned int>(_token_next.id) - static_cast<unsigned int>(tokenid::int2x2)) % 3;
		break;
	case tokenid::min16int:
	case tokenid::min16int2:
	case tokenid::min16int3:
	case tokenid::min16int4:
		type.base = type::t_min16int;
		type.rows = 1 + (static_cast<unsigned int>(_token_next.id) - static_cast<unsigned int>(tokenid::min16int));
		type.cols = 1;
		break;
	case tokenid::uint_:
	case tokenid::uint2:
	case tokenid::uint3:
	case tokenid::uint4:
		type.base = type::t_uint;
		type.rows = 1 + (static_cast<unsigned int>(_token_next.id) - static_cast<unsigned int>(tokenid::uint_));
		type.cols = 1;
		break;
	case tokenid::uint2x2:
	case tokenid::uint2x3:
	case tokenid::uint2x4:
	case tokenid::uint3x2:
	case tokenid::uint3x3:
	case tokenid::uint3x4:
	case tokenid::uint4x2:
	case tokenid::uint4x3:
	case tokenid::uint4x4:
		type.base = type::t_uint;
		type.rows = 2 + (static_cast<unsigned int>(_token_next.id) - static_cast<unsigned int>(tokenid::uint2x2)) / 3;
		type.cols = 2 + (static_cast<unsigned int>(_token_next.id) - static_cast<unsigned int>(tokenid::uint2x2)) % 3;
		break;
	case tokenid::min16uint:
	case tokenid::min16uint2:
	case tokenid::min16uint3:
	case tokenid::min16uint4:
		type.base = type::t_min16uint;
		type.rows = 1 + (static_cast<unsigned int>(_token_next.id) - static_cast<unsigned int>(tokenid::min16uint));
		type.cols = 1;
		break;
	case tokenid::float_:
	case tokenid::float2:
	case tokenid::float3:
	case tokenid::float4:
		type.base = type::t_float;
		type.rows = 1 + (static_cast<unsigned int>(_token_next.id) - static_cast<unsigned int>(tokenid::float_));
		type.cols = 1;
		break;
	case tokenid::float2x2:
	case tokenid::float2x3:
	case tokenid::float2x4:
	case tokenid::float3x2:
	case tokenid::float3x3:
	case tokenid::float3x4:
	case tokenid::float4x2:
	case tokenid::float4x3:
	case tokenid::float4x4:
		type.base = type::t_float;
		type.rows = 2 + (static_cast<unsigned int>(_token_next.id) - static_cast<unsigned int>(tokenid::float2x2)) / 3;
		type.cols = 2 + (static_cast<unsigned int>(_token_next.id) - static_cast<unsigned int>(tokenid::float2x2)) % 3;
		break;
	case tokenid::min16float:
	case tokenid::min16float2:
	case tokenid::min16float3:
	case tokenid::min16float4:
		type.base = type::t_min16float;
		type.rows = 1 + (static_cast<unsigned int>(_token_next.id) - static_cast<unsigned int>(tokenid::min16float));
		type.cols = 1;
		break;
	case tokenid::string_:
		type.base = type::t_string;
		break;
	case tokenid::texture1d:
		type.base = type::t_texture1d;
		break;
	case tokenid::texture2d:
		type.base = type::t_texture2d;
		break;
	case tokenid::texture3d:
		type.base = type::t_texture3d;
		break;
	default:
		return false;
	}

	consume();

	return true;
}
bool reshadefx::parser::accept_type_qualifiers(type &type)
{
	unsigned int qualifiers = 0;

	// Storage
	if (accept(tokenid::extern_))
		qualifiers |= type::q_extern;
	if (accept(tokenid::static_))
		qualifiers |= type::q_static;
	if (accept(tokenid::uniform_))
		qualifiers |= type::q_uniform;
	if (accept(tokenid::volatile_))
		qualifiers |= type::q_volatile;
	if (accept(tokenid::precise))
		qualifiers |= type::q_precise;
	if (accept(tokenid::groupshared))
		qualifiers |= type::q_groupshared;

	if (accept(tokenid::in))
		qualifiers |= type::q_in;
	if (accept(tokenid::out))
		qualifiers |= type::q_out;
	if (accept(tokenid::inout))
		qualifiers |= type::q_inout;

	// Modifiers
	if (accept(tokenid::const_))
		qualifiers |= type::q_const;

	// Interpolation
	if (accept(tokenid::linear))
		qualifiers |= type::q_linear;
	if (accept(tokenid::noperspective))
		qualifiers |= type::q_noperspective;
	if (accept(tokenid::centroid))
		qualifiers |= type::q_centroid;
	if (accept(tokenid::nointerpolation))
		qualifiers |= type::q_nointerpolation;

	if (qualifiers == 0)
		return false;
	if ((type.qualifiers & qualifiers) == qualifiers)
		warning(_token.location, 3048, "duplicate usages specified");

	type.qualifiers |= qualifiers;

	// Continue parsing potential additional qualifiers until no more are found
	accept_type_qualifiers(type);

	return true;
}

bool reshadefx::parser::accept_unary_op()
{
	switch (_token_next.id)
	{
	case tokenid::exclaim: // !x (logical not)
	case tokenid::plus: // +x
	case tokenid::minus: // -x (negate)
	case tokenid::tilde: // ~x (bitwise not)
	case tokenid::plus_plus: // ++x
	case tokenid::minus_minus: // --x
		break;
	default:
		return false;
	}

	consume();

	return true;
}
bool reshadefx::parser::accept_postfix_op()
{
	switch (_token_next.id)
	{
	case tokenid::plus_plus: // ++x
	case tokenid::minus_minus: // --x
		break;
	default:
		return false;
	}

	consume();

	return true;
}
bool reshadefx::parser::peek_multary_op(unsigned int &precedence) const
{
	// Precedence values taken from https://cppreference.com/w/cpp/language/operator_precedence
	switch (_token_next.id)
	{
	case tokenid::question: precedence = 1; break; // x ? a : b
	case tokenid::pipe_pipe: precedence = 2; break; // a || b (logical or)
	case tokenid::ampersand_ampersand: precedence = 3; break; // a && b (logical and)
	case tokenid::pipe: precedence = 4; break; // a | b (bitwise or)
	case tokenid::caret: precedence = 5; break; // a ^ b (bitwise xor)
	case tokenid::ampersand: precedence = 6; break; // a & b (bitwise and)
	case tokenid::equal_equal: precedence = 7; break; // a == b (equal)
	case tokenid::exclaim_equal: precedence = 7; break; // a != b (not equal)
	case tokenid::less: precedence = 8; break; // a < b
	case tokenid::greater: precedence = 8; break; // a > b
	case tokenid::less_equal: precedence = 8; break; // a <= b
	case tokenid::greater_equal: precedence = 8; break; // a >= b
	case tokenid::less_less: precedence = 9; break; // a << b (left shift)
	case tokenid::greater_greater: precedence = 9; break; // a >> b (right shift)
	case tokenid::plus: precedence = 10; break; // a + b (add)
	case tokenid::minus: precedence = 10; break; // a - b (subtract)
	case tokenid::star: precedence = 11; break; // a * b (multiply)
	case tokenid::slash: precedence = 11; break; // a / b (divide)
	case tokenid::percent: precedence = 11; break; // a % b (modulo)
	default:
		return false;
	}

	// Do not consume token yet since the expression may be skipped due to precedence
	return true;
}
bool reshadefx::parser::accept_assignment_op()
{
	switch (_token_next.id)
	{
	case tokenid::equal: // a = b
	case tokenid::percent_equal: // a %= b
	case tokenid::ampersand_equal: // a &= b
	case tokenid::star_equal: // a *= b
	case tokenid::plus_equal: // a += b
	case tokenid::minus_equal: // a -= b
	case tokenid::slash_equal: // a /= b
	case tokenid::less_less_equal: // a <<= b
	case tokenid::greater_greater_equal: // a >>= b
	case tokenid::caret_equal: // a ^= b
	case tokenid::pipe_equal: // a |= b
		break;
	default:
		return false;
	}

	consume();

	return true;
}

bool reshadefx::parser::parse_expression(expression &exp)
{
	// Parse first expression
	if (!parse_expression_assignment(exp))
		return false;

	// Continue parsing if an expression sequence is next (in the form "a, b, c, ...")
	while (accept(','))
		// Overwrite 'exp' since conveniently the last expression in the sequence is the result
		if (!parse_expression_assignment(exp))
			return false;

	return true;
}

bool reshadefx::parser::parse_expression_unary(expression &exp)
{
	auto location = _token_next.location;

	// Check if a prefix operator exists
	if (accept_unary_op())
	{
		// Remember the operator token before parsing the expression that follows it
		const tokenid op = _token.id;

		// Parse the actual expression
		if (!parse_expression_unary(exp))
			return false;

		// Unary operators are only valid on basic types
		if (!exp.type.is_scalar() && !exp.type.is_vector() && !exp.type.is_matrix())
			return error(exp.location, 3022, "scalar, vector, or matrix expected"), false;

		// Special handling for the "++" and "--" operators
		if (op == tokenid::plus_plus || op == tokenid::minus_minus)
		{
			if (exp.type.has(type::q_const) || !exp.is_lvalue)
				return error(location, 3025, "l-value specifies const object"), false;

			// Create a constant one in the type of the expression
			constant one = {};
			for (unsigned int i = 0; i < exp.type.components(); ++i)
				if (exp.type.is_floating_point()) one.as_float[i] = 1.0f; else one.as_uint[i] = 1u;
			const codegen::id constant_one = _codegen->emit_constant(exp.type, one);

			const codegen::id value = _codegen->emit_load(exp);
			const codegen::id result = _codegen->emit_binary_op(location, op, exp.type, value, constant_one);

			// The "++" and "--" operands modify the source variable, so store result back into it
			_codegen->emit_store(exp, result);
		}
		else if (op != tokenid::plus) // Ignore "+" operator since it does not actually do anything
		{
			// The "~" bitwise operator is only valid on integral types
			if (op == tokenid::tilde && !exp.type.is_integral())
				return error(exp.location, 3082, "int or unsigned int type required"), false;
			// The logical not operator expects a boolean type as input, so perform cast if necessary
			if (op == tokenid::exclaim && !exp.type.is_boolean())
				exp.add_cast_operation({ type::t_bool, exp.type.rows, exp.type.cols }); // Note: The result will be boolean as well

			// Constant expressions can be evaluated at compile time
			if (!exp.evaluate_constant_expression(op))
			{
				const codegen::id value = _codegen->emit_load(exp);
				const codegen::id result = _codegen->emit_unary_op(location, op, exp.type, value);

				exp.reset_to_rvalue(location, result, exp.type);
			}
		}
	}
	else if (accept('('))
	{
		// Note: This backup may get overridden in 'accept_type_class', but should point to the same token still
		backup();

		// Check if this is a C-style cast expression
		if (type cast_type = {}; accept_type_class(cast_type))
		{
			if (peek('('))
			{
				// This is not a C-style cast but a constructor call, so need to roll-back and parse that instead
				restore();
			}
			else if (expect(')'))
			{
				// Parse the expression behind cast operator
				if (!parse_expression_unary(exp))
					return false;

				// Check if the types already match, in which case there is nothing to do
				if (exp.type == cast_type)
					return true;

				// Check if a cast between these types is valid
				if (!type::rank(exp.type, cast_type))
					return error(location, 3017, "cannot convert these types (from " + exp.type.description() + " to " + cast_type.description() + ')'), false;

				exp.add_cast_operation(cast_type);
				return true;
			}
			else
			{
				// Type name was not followed by a closing parenthesis
				return false;
			}
		}

		// Parse expression between the parentheses
		if (!parse_expression(exp) || !expect(')'))
			return false;
	}
	else if (accept('{'))
	{
		bool is_constant = true;
		std::vector<expression> elements;
		type composite_type = { type::t_void, 1, 1 };

		while (!peek('}'))
		{
			// There should be a comma between arguments
			if (!elements.empty() && !expect(','))
				return consume_until('}'), false;

			// Initializer lists might contain a comma at the end, so break out of the loop if nothing follows afterwards
			if (peek('}'))
				break;

			expression &element_exp = elements.emplace_back();

			// Parse the argument expression
			if (!parse_expression_assignment(element_exp))
				return consume_until('}'), false;

			if (element_exp.type.is_array())
				return error(element_exp.location, 3119, "arrays cannot be multi-dimensional"), consume_until('}'), false;
			if (composite_type.base != type::t_void && element_exp.type.definition != composite_type.definition)
				return error(element_exp.location, 3017, "cannot convert these types (from " + element_exp.type.description() + " to " + composite_type.description() + ')'), false;

			is_constant &= element_exp.is_constant; // Result is only constant if all arguments are constant
			composite_type = type::merge(composite_type, element_exp.type);
		}

		// Constant arrays can be constructed at compile time
		if (is_constant)
		{
			constant result = {};
			for (expression &element_exp : elements)
			{
				element_exp.add_cast_operation(composite_type);
				result.array_data.push_back(element_exp.constant);
			}

			composite_type.array_length = static_cast<unsigned int>(elements.size());

			exp.reset_to_rvalue_constant(location, std::move(result), composite_type);
		}
		else
		{
			// Resolve all access chains
			for (expression &element_exp : elements)
			{
				element_exp.add_cast_operation(composite_type);
				const codegen::id element_value = _codegen->emit_load(element_exp);
				element_exp.reset_to_rvalue(element_exp.location, element_value, composite_type);
			}

			composite_type.array_length = static_cast<unsigned int>(elements.size());

			const codegen::id result = _codegen->emit_construct(location, composite_type, elements);
			exp.reset_to_rvalue(location, result, composite_type);
		}

		return expect('}');
	}
	else if (accept(tokenid::true_literal))
	{
		exp.reset_to_rvalue_constant(location, true);
	}
	else if (accept(tokenid::false_literal))
	{
		exp.reset_to_rvalue_constant(location, false);
	}
	else if (accept(tokenid::int_literal))
	{
		exp.reset_to_rvalue_constant(location, _token.literal_as_int);
	}
	else if (accept(tokenid::uint_literal))
	{
		exp.reset_to_rvalue_constant(location, _token.literal_as_uint);
	}
	else if (accept(tokenid::float_literal))
	{
		exp.reset_to_rvalue_constant(location, _token.literal_as_float);
	}
	else if (accept(tokenid::double_literal))
	{
		// Convert double literal to float literal for now
		warning(location, 5000, "double literal truncated to float literal");

		exp.reset_to_rvalue_constant(location, static_cast<float>(_token.literal_as_double));
	}
	else if (accept(tokenid::string_literal))
	{
		std::string value = std::move(_token.literal_as_string);

		// Multiple string literals in sequence are concatenated into a single string literal
		while (accept(tokenid::string_literal))
			value += _token.literal_as_string;

		exp.reset_to_rvalue_constant(location, std::move(value));
	}
	else if (type type = {}; accept_type_class(type)) // Check if this is a constructor call expression
	{
		if (!expect('('))
			return false;
		if (!type.is_numeric())
			return error(location, 3037, "constructors only defined for numeric base types"), false;

		// Empty constructors do not exist
		if (accept(')'))
			return error(location, 3014, "incorrect number of arguments to numeric-type constructor"), false;

		// Parse entire argument expression list
		bool is_constant = true;
		unsigned int num_components = 0;
		std::vector<expression> arguments;

		while (!peek(')'))
		{
			// There should be a comma between arguments
			if (!arguments.empty() && !expect(','))
				return false;

			expression &argument_exp = arguments.emplace_back();

			// Parse the argument expression
			if (!parse_expression_assignment(argument_exp))
				return false;

			// Constructors are only defined for numeric base types
			if (!argument_exp.type.is_numeric())
				return error(argument_exp.location, 3017, "cannot convert non-numeric types"), false;

			is_constant &= argument_exp.is_constant; // Result is only constant if all arguments are constant
			num_components += argument_exp.type.components();
		}

		// The list should be terminated with a parenthesis
		if (!expect(')'))
			return false;

		// The total number of argument elements needs to match the number of elements in the result type
		if (num_components != type.components())
			return error(location, 3014, "incorrect number of arguments to numeric-type constructor"), false;

		assert(num_components > 0 && num_components <= 16 && !type.is_array());

		if (is_constant) // Constants can be converted at compile time
		{
			constant result = {};
			unsigned int i = 0;
			for (expression &argument_exp : arguments)
			{
				argument_exp.add_cast_operation({ type.base, argument_exp.type.rows, argument_exp.type.cols });
				for (unsigned int k = 0; k < argument_exp.type.components(); ++k)
					result.as_uint[i++] = argument_exp.constant.as_uint[k];
			}

			exp.reset_to_rvalue_constant(location, std::move(result), type);
		}
		else if (arguments.size() > 1)
		{
			// Flatten all arguments to a list of scalars
			for (auto it = arguments.begin(); it != arguments.end();)
			{
				// Argument is a scalar already, so only need to cast it
				if (it->type.is_scalar())
				{
					expression &argument_exp = *it++;

					struct type scalar_type = argument_exp.type;
					scalar_type.base = type.base;
					argument_exp.add_cast_operation(scalar_type);

					argument_exp.reset_to_rvalue(argument_exp.location, _codegen->emit_load(argument_exp), scalar_type);
				}
				else
				{
					const expression argument_exp = std::move(*it);
					it = arguments.erase(it);

					// Convert to a scalar value and re-enter the loop in the next iteration (in case a cast is necessary too)
					for (unsigned int i = argument_exp.type.components(); i > 0; --i)
					{
						expression argument_scalar_exp = argument_exp;
						argument_scalar_exp.add_constant_index_access(i - 1);

						it = arguments.insert(it, argument_scalar_exp);
					}
				}
			}

			const codegen::id result = _codegen->emit_construct(location, type, arguments);

			exp.reset_to_rvalue(location, result, type);
		}
		else // A constructor call with a single argument is identical to a cast
		{
			assert(!arguments.empty());

			// Reset expression to only argument and add cast to expression access chain
			exp = std::move(arguments[0]); exp.add_cast_operation(type);
		}
	}
	// At this point only identifiers are left to check and resolve
	else
	{
		std::string identifier;
		scoped_symbol symbol;
		if (!accept_symbol(identifier, symbol))
			return false;

		// Check if this is a function call or variable reference
		if (accept('('))
		{
			// Can only call symbols that are functions, but do not abort yet if no symbol was found since the identifier may reference an intrinsic
			if (symbol.id && symbol.op != symbol_type::function)
				return error(location, 3005, "identifier '" + identifier + "' represents a variable, not a function"), false;

			// Parse entire argument expression list
			std::vector<expression> arguments;

			while (!peek(')'))
			{
				// There should be a comma between arguments
				if (!arguments.empty() && !expect(','))
					return false;

				expression &argument_exp = arguments.emplace_back();

				// Parse the argument expression
				if (!parse_expression_assignment(argument_exp))
					return false;
			}

			// The list should be terminated with a parenthesis
			if (!expect(')'))
				return false;

			// Function calls can only be made from within functions
			if (!_codegen->is_in_function())
				return error(location, 3005, "invalid function call outside of a function"), false;

			// Try to resolve the call by searching through both function symbols and intrinsics
			bool undeclared = !symbol.id, ambiguous = false;

			if (!resolve_function_call(identifier, arguments, symbol.scope, symbol, ambiguous))
			{
				if (undeclared)
					error(location, 3004, "undeclared identifier or no matching intrinsic overload for '" + identifier + '\'');
				else if (ambiguous)
					error(location, 3067, "ambiguous function call to '" + identifier + '\'');
				else
					error(location, 3013, "no matching function overload for '" + identifier + '\'');
				return false;
			}

			assert(symbol.function != nullptr);

			std::vector<expression> parameters(arguments.size());

			// We need to allocate some temporary variables to pass in and load results from pointer parameters
			for (size_t i = 0; i < arguments.size(); ++i)
			{
				const auto &param_type = symbol.function->parameter_list[i].type;

				if (param_type.has(type::q_out) && (!arguments[i].is_lvalue || (arguments[i].type.has(type::q_const) && !arguments[i].type.is_object())))
					return error(arguments[i].location, 3025, "l-value specifies const object for an 'out' parameter"), false;

				if (arguments[i].type.components() > param_type.components())
					warning(arguments[i].location, 3206, "implicit truncation of vector type");

				if (symbol.op == symbol_type::function || param_type.has(type::q_out))
				{
					if (param_type.is_object() || param_type.has(type::q_groupshared) /* Special case for atomic intrinsics */)
					{
						if (arguments[i].type != param_type)
							return error(location, 3004, "no matching intrinsic overload for '" + identifier + '\''), false;

						assert(arguments[i].is_lvalue);

						// Do not shadow object or pointer parameters to function calls
						size_t chain_index = 0;
						const codegen::id access_chain = _codegen->emit_access_chain(arguments[i], chain_index);
						parameters[i].reset_to_lvalue(arguments[i].location, access_chain, param_type);
						assert(chain_index == arguments[i].chain.size());

						// This is referencing a l-value, but want to avoid copying below
						parameters[i].is_lvalue = false;
					}
					else
					{
						// All user-defined functions actually accept pointers as arguments, same applies to intrinsics with 'out' parameters
						const codegen::id temp_variable = _codegen->define_variable(arguments[i].location, param_type);
						parameters[i].reset_to_lvalue(arguments[i].location, temp_variable, param_type);
					}
				}
				else
				{
					expression argument_exp = arguments[i];
					argument_exp.add_cast_operation(param_type);
					const codegen::id argument_value = _codegen->emit_load(argument_exp);
					parameters[i].reset_to_rvalue(argument_exp.location, argument_value, param_type);

					// Keep track of whether the parameter is a constant for code generation (this makes the expression invalid for all other uses)
					parameters[i].is_constant = argument_exp.is_constant;
				}
			}

			// Copy in parameters from the argument access chains to parameter variables
			for (size_t i = 0; i < arguments.size(); ++i)
			{
				// Only do this for pointer parameters as discovered above
				if (parameters[i].is_lvalue && parameters[i].type.has(type::q_in) && !parameters[i].type.is_object())
				{
					expression argument_exp = arguments[i];
					argument_exp.add_cast_operation(parameters[i].type);
					const codegen::id argument_value = _codegen->emit_load(argument_exp);
					_codegen->emit_store(parameters[i], argument_value);
				}
			}

			// Check if the call resolving found an intrinsic or function and invoke the corresponding code
			const codegen::id result = symbol.op == symbol_type::function ?
				_codegen->emit_call(location, symbol.id, symbol.type, parameters) :
				_codegen->emit_call_intrinsic(location, symbol.id, symbol.type, parameters);

			exp.reset_to_rvalue(location, result, symbol.type);

			// Copy out parameters from parameter variables back to the argument access chains
			for (size_t i = 0; i < arguments.size(); ++i)
			{
				// Only do this for pointer parameters as discovered above
				if (parameters[i].is_lvalue && parameters[i].type.has(type::q_out) && !parameters[i].type.is_object())
				{
					expression argument_exp = parameters[i];
					argument_exp.add_cast_operation(arguments[i].type);
					const codegen::id argument_value = _codegen->emit_load(argument_exp);
					_codegen->emit_store(arguments[i], argument_value);
				}
			}

			if (_current_function != nullptr)
			{
				// Calling a function makes the caller inherit all sampler and storage object references from the callee
				_current_function->referenced_samplers.insert(symbol.function->referenced_samplers.begin(), symbol.function->referenced_samplers.end());
				_current_function->referenced_storages.insert(symbol.function->referenced_storages.begin(), symbol.function->referenced_storages.end());
			}
		}
		else if (symbol.op == symbol_type::invalid)
		{
			// Show error if no symbol matching the identifier was found
			return error(location, 3004, "undeclared identifier '" + identifier + '\''), false;
		}
		else if (symbol.op == symbol_type::variable)
		{
			assert(symbol.id != 0);
			// Simply return the pointer to the variable, dereferencing is done on site where necessary
			exp.reset_to_lvalue(location, symbol.id, symbol.type);

			if (_current_function != nullptr &&
				symbol.scope.level == symbol.scope.namespace_level && symbol.id != 0xFFFFFFFF) // Ignore invalid symbols that were added during error recovery
			{
				// Keep track of any global sampler or storage objects referenced in the current function
				if (symbol.type.is_sampler())
					_current_function->referenced_samplers.insert(symbol.id);
				if (symbol.type.is_storage())
					_current_function->referenced_storages.insert(symbol.id);
			}
		}
		else if (symbol.op == symbol_type::constant)
		{
			// Constants are loaded into the access chain
			exp.reset_to_rvalue_constant(location, symbol.constant, symbol.type);
		}
		else
		{
			// Can only reference variables and constants by name, functions need to be called
			return error(location, 3005, "identifier '" + identifier + "' represents a function, not a variable"), false;
		}
	}

	while (!peek(tokenid::end_of_file))
	{
		location = _token_next.location;

		// Check if a postfix operator exists
		if (accept_postfix_op())
		{
			// Unary operators are only valid on basic types
			if (!exp.type.is_scalar() && !exp.type.is_vector() && !exp.type.is_matrix())
				return error(exp.location, 3022, "scalar, vector, or matrix expected"), false;
			if (exp.type.has(type::q_const) || !exp.is_lvalue)
				return error(exp.location, 3025, "l-value specifies const object"), false;

			// Create a constant one in the type of the expression
			constant one = {};
			for (unsigned int i = 0; i < exp.type.components(); ++i)
				if (exp.type.is_floating_point()) one.as_float[i] = 1.0f; else one.as_uint[i] = 1u;
			const codegen::id constant_one = _codegen->emit_constant(exp.type, one);

			const codegen::id value = _codegen->emit_load(exp, true);
			const codegen::id result = _codegen->emit_binary_op(location, _token.id, exp.type, value, constant_one);

			// The "++" and "--" operands modify the source variable, so store result back into it
			_codegen->emit_store(exp, result);

			// All postfix operators return a r-value rather than a l-value to the variable
			exp.reset_to_rvalue(location, value, exp.type);
		}
		else if (accept('.'))
		{
			if (!expect(tokenid::identifier))
				return false;

			location = std::move(_token.location);
			const std::string subscript = std::move(_token.literal_as_string);

			if (accept('(')) // Methods (function calls on types) are not supported right now
			{
				if (!exp.type.is_struct() || exp.type.is_array())
					error(location, 3087, "object does not have methods");
				else
					error(location, 3088, "structures do not have methods");
				return false;
			}
			else if (exp.type.is_array()) // Arrays do not have subscripts
			{
				error(location, 3018, "invalid subscript on array");
				return false;
			}
			else if (exp.type.is_vector())
			{
				const size_t length = subscript.size();
				if (length > 4)
					return error(location, 3018, "invalid subscript '" + subscript + "', swizzle too long"), false;

				bool is_const = false;
				signed char offsets[4] = { -1, -1, -1, -1 };
				enum { xyzw, rgba, stpq } set[4];

				for (size_t i = 0; i < length; ++i)
				{
					switch (subscript[i])
					{
					case 'x': offsets[i] = 0, set[i] = xyzw; break;
					case 'y': offsets[i] = 1, set[i] = xyzw; break;
					case 'z': offsets[i] = 2, set[i] = xyzw; break;
					case 'w': offsets[i] = 3, set[i] = xyzw; break;
					case 'r': offsets[i] = 0, set[i] = rgba; break;
					case 'g': offsets[i] = 1, set[i] = rgba; break;
					case 'b': offsets[i] = 2, set[i] = rgba; break;
					case 'a': offsets[i] = 3, set[i] = rgba; break;
					case 's': offsets[i] = 0, set[i] = stpq; break;
					case 't': offsets[i] = 1, set[i] = stpq; break;
					case 'p': offsets[i] = 2, set[i] = stpq; break;
					case 'q': offsets[i] = 3, set[i] = stpq; break;
					default:
						return error(location, 3018, "invalid subscript '" + subscript + '\''), false;
					}

					if (i > 0 && (set[i] != set[i - 1]))
						return error(location, 3018, "invalid subscript '" + subscript + "', mixed swizzle sets"), false;
					if (static_cast<unsigned int>(offsets[i]) >= exp.type.rows)
						return error(location, 3018, "invalid subscript '" + subscript + "', swizzle out of range"), false;

					// The result is not modifiable if a swizzle appears multiple times
					for (size_t k = 0; k < i; ++k)
						if (offsets[k] == offsets[i]) {
							is_const = true;
							break;
						}
				}

				// Add swizzle to current access chain
				exp.add_swizzle_access(offsets, static_cast<unsigned int>(length));

				if (is_const)
					exp.type.qualifiers |= type::q_const;
			}
			else if (exp.type.is_matrix())
			{
				const size_t length = subscript.size();
				if (length < 3)
					return error(location, 3018, "invalid subscript '" + subscript + '\''), false;

				bool is_const = false;
				signed char offsets[4] = { -1, -1, -1, -1 };
				const int set = subscript[1] == 'm';
				const int coefficient = !set;

				for (size_t i = 0, j = 0; i < length; i += 3 + set, ++j)
				{
					if (subscript[i] != '_' ||
						subscript[i + set + 1] < '0' + coefficient ||
						subscript[i + set + 1] > '3' + coefficient ||
						subscript[i + set + 2] < '0' + coefficient ||
						subscript[i + set + 2] > '3' + coefficient)
						return error(location, 3018, "invalid subscript '" + subscript + '\''), false;
					if (set && subscript[i + 1] != 'm')
						return error(location, 3018, "invalid subscript '" + subscript + "', mixed swizzle sets"), false;

					const unsigned int row = static_cast<unsigned int>((subscript[i + set + 1] - '0') - coefficient);
					const unsigned int col = static_cast<unsigned int>((subscript[i + set + 2] - '0') - coefficient);

					if ((row >= exp.type.rows || col >= exp.type.cols) || j > 3)
						return error(location, 3018, "invalid subscript '" + subscript + "', swizzle out of range"), false;

					offsets[j] = static_cast<signed char>(row * 4 + col);

					// The result is not modifiable if a swizzle appears multiple times
					for (size_t k = 0; k < j; ++k)
						if (offsets[k] == offsets[j]) {
							is_const = true;
							break;
						}
				}

				// Add swizzle to current access chain
				exp.add_swizzle_access(offsets, static_cast<unsigned int>(length / (3 + set)));

				if (is_const)
					exp.type.qualifiers |= type::q_const;
			}
			else if (exp.type.is_struct())
			{
				const std::vector<struct_member_info> &member_list = _codegen->get_struct(exp.type.definition).member_list;

				// Find member with matching name is structure definition
				uint32_t member_index = 0;
				for (const struct_member_info &member : member_list) {
					if (member.name == subscript)
						break;
					++member_index;
				}

				if (member_index >= member_list.size())
					return error(location, 3018, "invalid subscript '" + subscript + '\''), false;

				// Add field index to current access chain
				exp.add_member_access(member_index, member_list[member_index].type);
			}
			else if (exp.type.is_scalar())
			{
				const size_t length = subscript.size();
				if (length > 4)
					return error(location, 3018, "invalid subscript '" + subscript + "', swizzle too long"), false;

				for (size_t i = 0; i < length; ++i)
					if ((subscript[i] != 'x' && subscript[i] != 'r' && subscript[i] != 's') || i > 3)
						return error(location, 3018, "invalid subscript '" + subscript + '\''), false;

				// Promote scalar to vector type using cast
				auto target_type = exp.type;
				target_type.rows = static_cast<unsigned int>(length);

				exp.add_cast_operation(target_type);

				if (length > 1)
					exp.type.qualifiers |= type::q_const;
			}
			else
			{
				error(location, 3018, "invalid subscript '" + subscript + '\'');
				return false;
			}
		}
		else if (accept('['))
		{
			if (!exp.type.is_array() && !exp.type.is_vector() && !exp.type.is_matrix())
				return error(_token.location, 3121, "array, matrix, vector, or indexable object type expected in index expression"), false;

			// Parse index expression
			expression index_exp;
			if (!parse_expression(index_exp) || !expect(']'))
				return false;

			if (!index_exp.type.is_scalar() || !index_exp.type.is_integral())
				return error(index_exp.location, 3120, "invalid type for index - index must be an integer scalar"), false;

			// Add index expression to current access chain
			if (index_exp.is_constant)
			{
				// Check array bounds if known
				if (exp.type.is_bounded_array() && index_exp.constant.as_uint[0] >= exp.type.array_length)
					return error(index_exp.location, 3504, "array index out of bounds"), false;

				exp.add_constant_index_access(index_exp.constant.as_uint[0]);
			}
			else
			{
				if (exp.is_constant)
				{
					// To handle a dynamic index into a constant means we need to create a local variable first or else any of the indexing instructions do not work
					const codegen::id temp_variable = _codegen->define_variable(location, exp.type, std::string(), false, _codegen->emit_constant(exp.type, exp.constant));
					exp.reset_to_lvalue(exp.location, temp_variable, exp.type);
				}

				exp.add_dynamic_index_access(_codegen->emit_load(index_exp));
			}
		}
		else
		{
			break;
		}
	}

	return true;
}

bool reshadefx::parser::parse_expression_multary(expression &lhs, unsigned int left_precedence)
{
	// Parse left hand side of the expression
	if (!parse_expression_unary(lhs))
		return false;

	// Check if an operator exists so that this is a binary or ternary expression
	unsigned int right_precedence;

	while (peek_multary_op(right_precedence))
	{
		// Only process this operator if it has a lower precedence than the current operation, otherwise leave it for later and abort
		if (right_precedence <= left_precedence)
			break;

		// Finally consume the operator token
		consume();

		const tokenid op = _token.id;

		// Check if this is a binary or ternary operation
		if (op != tokenid::question)
		{
#if RESHADEFX_SHORT_CIRCUIT
			codegen::id lhs_block = 0;
			codegen::id rhs_block = 0;
			codegen::id merge_block = 0;

			// Switch block to a new one before parsing right-hand side value in case it needs to be skipped during short-circuiting
			if (op == tokenid::ampersand_ampersand || op == tokenid::pipe_pipe)
			{
				lhs_block = _codegen->set_block(0);
				rhs_block = _codegen->create_block();
				merge_block = _codegen->create_block();

				_codegen->enter_block(rhs_block);
			}
#endif
			// Parse the right hand side of the binary operation
			expression rhs;
			if (!parse_expression_multary(rhs, right_precedence))
				return false;

			// Deduce the result base type based on implicit conversion rules
			type type = type::merge(lhs.type, rhs.type);
			bool is_bool_result = false;

			// Do some error checking depending on the operator
			if (op == tokenid::equal_equal || op == tokenid::exclaim_equal)
			{
				// Equality checks return a boolean value
				is_bool_result = true;

				// Cannot check equality between incompatible types
				if (lhs.type.is_array() || rhs.type.is_array() || lhs.type.definition != rhs.type.definition)
					return error(rhs.location, 3020, "type mismatch"), false;
			}
			else if (op == tokenid::ampersand || op == tokenid::pipe || op == tokenid::caret)
			{
				if (type.is_boolean())
					type.base = type::t_int;

				// Cannot perform bitwise operations on non-integral types
				if (!lhs.type.is_integral())
					return error(lhs.location, 3082, "int or unsigned int type required"), false;
				if (!rhs.type.is_integral())
					return error(rhs.location, 3082, "int or unsigned int type required"), false;
			}
			else
			{
				if (op == tokenid::ampersand_ampersand || op == tokenid::pipe_pipe)
					type.base = type::t_bool;
				else if (op == tokenid::less || op == tokenid::less_equal || op == tokenid::greater || op == tokenid::greater_equal)
					is_bool_result = true; // Logical operations return a boolean value
				else if (type.is_boolean())
					type.base = type::t_int; // Arithmetic with boolean values treats the operands as integers

				// Cannot perform arithmetic operations on non-basic types
				if (!lhs.type.is_scalar() && !lhs.type.is_vector() && !lhs.type.is_matrix())
					return error(lhs.location, 3022, "scalar, vector, or matrix expected"), false;
				if (!rhs.type.is_scalar() && !rhs.type.is_vector() && !rhs.type.is_matrix())
					return error(rhs.location, 3022, "scalar, vector, or matrix expected"), false;
			}

			// Perform implicit type conversion
			if (lhs.type.components() > type.components())
				warning(lhs.location, 3206, "implicit truncation of vector type");
			if (rhs.type.components() > type.components())
				warning(rhs.location, 3206, "implicit truncation of vector type");

			lhs.add_cast_operation(type);
			rhs.add_cast_operation(type);

#if RESHADEFX_SHORT_CIRCUIT
			// Reset block to left-hand side since the load of the left-hand side value has to happen in there
			if (op == tokenid::ampersand_ampersand || op == tokenid::pipe_pipe)
				_codegen->set_block(lhs_block);
#endif

			// Constant expressions can be evaluated at compile time
			if (rhs.is_constant && lhs.evaluate_constant_expression(op, rhs.constant))
				continue;

			const codegen::id lhs_value = _codegen->emit_load(lhs);

#if RESHADEFX_SHORT_CIRCUIT
			// Short circuit for logical && and || operators
			if (op == tokenid::ampersand_ampersand || op == tokenid::pipe_pipe)
			{
				// Emit "if ( lhs) result = rhs" for && expression
				codegen::id condition_value = lhs_value;
				// Emit "if (!lhs) result = rhs" for || expression
				if (op == tokenid::pipe_pipe)
					condition_value = _codegen->emit_unary_op(lhs.location, tokenid::exclaim, type, lhs_value);

				_codegen->leave_block_and_branch_conditional(condition_value, rhs_block, merge_block);

				_codegen->set_block(rhs_block);
				// Only load value of right hand side expression after entering the second block
				const codegen::id rhs_value = _codegen->emit_load(rhs);
				_codegen->leave_block_and_branch(merge_block);

				_codegen->enter_block(merge_block);

				const codegen::id result_value = _codegen->emit_phi(lhs.location, condition_value, lhs_block, rhs_value, rhs_block, lhs_value, lhs_block, type);

				lhs.reset_to_rvalue(lhs.location, result_value, type);
				continue;
			}
#endif
			const codegen::id rhs_value = _codegen->emit_load(rhs);

			// Certain operations return a boolean type instead of the type of the input expressions
			if (is_bool_result)
				type = { type::t_bool, type.rows, type.cols };

			const codegen::id result_value = _codegen->emit_binary_op(lhs.location, op, type, lhs.type, lhs_value, rhs_value);

			lhs.reset_to_rvalue(lhs.location, result_value, type);
		}
		else
		{
			// A conditional expression needs a scalar or vector type condition
			if (!lhs.type.is_scalar() && !lhs.type.is_vector())
				return error(lhs.location, 3022, "boolean or vector expression expected"), false;

#if RESHADEFX_SHORT_CIRCUIT
			// Switch block to a new one before parsing first part in case it needs to be skipped during short-circuiting
			const codegen::id merge_block = _codegen->create_block();
			const codegen::id condition_block = _codegen->set_block(0);
			codegen::id true_block = _codegen->create_block();
			codegen::id false_block = _codegen->create_block();

			_codegen->enter_block(true_block);
#endif
			// Parse the first part of the right hand side of the ternary operation
			expression true_exp;
			if (!parse_expression(true_exp))
				return false;

			if (!expect(':'))
				return false;

#if RESHADEFX_SHORT_CIRCUIT
			// Switch block to a new one before parsing second part in case it needs to be skipped during short-circuiting
			_codegen->set_block(0);
			_codegen->enter_block(false_block);
#endif
			// Parse the second part of the right hand side of the ternary operation
			expression false_exp;
			if (!parse_expression_assignment(false_exp))
				return false;

			// Check that the condition dimension matches that of at least one side
			if (lhs.type.rows != true_exp.type.rows && lhs.type.cols != true_exp.type.cols)
				return error(lhs.location, 3020, "dimension of conditional does not match value"), false;

			// Check that the two value expressions can be converted between each other
			if (true_exp.type.array_length != false_exp.type.array_length || true_exp.type.definition != false_exp.type.definition)
				return error(false_exp.location, 3020, "type mismatch between conditional values"), false;

			// Deduce the result base type based on implicit conversion rules
			const type type = type::merge(true_exp.type, false_exp.type);

			if (true_exp.type.components() > type.components())
				warning(true_exp.location, 3206, "implicit truncation of vector type");
			if (false_exp.type.components() > type.components())
				warning(false_exp.location, 3206, "implicit truncation of vector type");

#if RESHADEFX_SHORT_CIRCUIT
			// Reset block to left-hand side since the load of the condition value has to happen in there
			_codegen->set_block(condition_block);
#else
			// The conditional operator instruction expects the condition to be a boolean type
			lhs.add_cast_operation({ type::t_bool, type.rows, 1 });
#endif
			true_exp.add_cast_operation(type);
			false_exp.add_cast_operation(type);

			// Load condition value from expression
			const codegen::id condition_value = _codegen->emit_load(lhs);

#if RESHADEFX_SHORT_CIRCUIT
			_codegen->leave_block_and_branch_conditional(condition_value, true_block, false_block);

			_codegen->set_block(true_block);
			// Only load true expression value after entering the first block
			const codegen::id true_value = _codegen->emit_load(true_exp);
			true_block = _codegen->leave_block_and_branch(merge_block);

			_codegen->set_block(false_block);
			// Only load false expression value after entering the second block
			const codegen::id false_value = _codegen->emit_load(false_exp);
			false_block = _codegen->leave_block_and_branch(merge_block);

			_codegen->enter_block(merge_block);

			const codegen::id result_value = _codegen->emit_phi(lhs.location, condition_value, condition_block, true_value, true_block, false_value, false_block, type);
#else
			const codegen::id true_value = _codegen->emit_load(true_exp);
			const codegen::id false_value = _codegen->emit_load(false_exp);

			const codegen::id result_value = _codegen->emit_ternary_op(lhs.location, op, type, condition_value, true_value, false_value);
#endif
			lhs.reset_to_rvalue(lhs.location, result_value, type);
		}
	}

	return true;
}

bool reshadefx::parser::parse_expression_assignment(expression &lhs)
{
	// Parse left hand side of the expression
	if (!parse_expression_multary(lhs))
		return false;

	// Check if an operator exists so that this is an assignment
	if (accept_assignment_op())
	{
		// Remember the operator token before parsing the expression that follows it
		const tokenid op = _token.id;

		// Parse right hand side of the assignment expression
		// This may be another assignment expression to support chains like "a = b = c = 0;"
		expression rhs;
		if (!parse_expression_assignment(rhs))
			return false;

		// Check if the assignment is valid
		if (lhs.type.has(type::q_const) || !lhs.is_lvalue)
			return error(lhs.location, 3025, "l-value specifies const object"), false;
		if (!type::rank(lhs.type, rhs.type))
			return error(rhs.location, 3020, "cannot convert these types (from " + rhs.type.description() + " to " + lhs.type.description() + ')'), false;

		// Cannot perform bitwise operations on non-integral types
		if (!lhs.type.is_integral() && (op == tokenid::ampersand_equal || op == tokenid::pipe_equal || op == tokenid::caret_equal))
			return error(lhs.location, 3082, "int or unsigned int type required"), false;

		// Perform implicit type conversion of right hand side value
		if (rhs.type.components() > lhs.type.components())
			warning(rhs.location, 3206, "implicit truncation of vector type");

		rhs.add_cast_operation(lhs.type);

		codegen::id result = _codegen->emit_load(rhs);

		// Check if this is an assignment with an additional arithmetic instruction
		if (op != tokenid::equal)
		{
			// Load value for modification
			const codegen::id value = _codegen->emit_load(lhs);

			// Handle arithmetic assignment operation
			result = _codegen->emit_binary_op(lhs.location, op, lhs.type, value, result);
		}

		// Write result back to variable
		_codegen->emit_store(lhs, result);

		// Return the result value since you can write assignments within expressions
		lhs.reset_to_rvalue(lhs.location, result, lhs.type);
	}

	return true;
}
