#include "scirpt/parser.h"
#include "common/result.h"
#include "common/string.h"
#include "common/string_array.h"
#include "parser.h"
#include "scirpt/ast.h"
#include "scirpt/lexer.h"
#include "scirpt/position.h"
#include "scirpt/token.h"
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#define TT(type) ScirptTokenType##type
#define AET(type) ScirptExprType##type

void scirpt_parser_error_destroy(ScirptParserError* error)
{
	heapstring_destroy(&error->message);
}

static inline ScirptExpr* alloc_expr(ScirptExpr data)
{
	ScirptExpr* expr = malloc(sizeof(ScirptExpr));
	*expr = data;
	return expr;
}
static inline void
error(ScirptParser* parser, HeapString message, ScirptPosition pos)
{
	scirpt_parser_error(parser, message, pos);
}
static inline void step(ScirptParser* parser) { scirpt_parser_step(parser); }
static inline ScirptExpr* step_alloc_expr(ScirptParser* parser, ScirptExpr data)
{
	step(parser);
	return alloc_expr(data);
}
static inline bool current_is(const ScirptParser* parser, ScirptTokenType type)
{
	return scirpt_parser_current_is(parser, type);
}
static inline bool done(const ScirptParser* parser)
{
	return scirpt_parser_done(parser);
}

ScirptParser*
scirpt_parser_new(const char* text, size_t text_length, ScirptLexer* lexer)
{
	ScirptParser* parser = malloc(sizeof(ScirptParser));
	scirpt_parser_construct(parser, text, text_length, lexer);
	return parser;
}

void scirpt_parser_delete(ScirptParser* parser)
{
	scirpt_parser_destroy(parser);
	free(parser);
}

ScirptExpr* scirpt_parser_next(ScirptParser* parser)
{
	if (done(parser)) {
		return alloc_expr((ScirptExpr) {
			.type = AET(Eof),
		});
	} else if (parser->current.type == TT(Semicolon)) {
		step(parser);
		while (current_is(parser, TT(Semicolon)))
			step(parser);
		return scirpt_parser_next(parser);
	} else {
		return scirpt_parser_parse_statement(parser);
	}
}

bool scirpt_parser_ok(const ScirptParser* parser) { return parser->ok; }

const ScirptParserErrorArray* scirpt_parser_errors(const ScirptParser* parser)
{
	return &parser->errors;
}

void scirpt_parser_construct(
	ScirptParser* parser,
	const char* text,
	size_t text_length,
	ScirptLexer* lexer
)
{
	ScirptParserErrorArray errors;
	scirpt_parser_error_array_construct(&errors);

	*parser = (ScirptParser) {
		.text = text,
		.text_length = text_length,
		.lexer = lexer,
		.current = scirpt_lexer_next(lexer),
		.errors = errors,
		.ok = true,
	};
}

void scirpt_parser_destroy(ScirptParser* parser)
{
	for (size_t i = 0; i < parser->errors.length; ++i)
		scirpt_parser_error_destroy(&parser->errors.data[i]);
	scirpt_parser_error_array_construct(&parser->errors);
}

ScirptExpr* scirpt_parser_parse_statement(ScirptParser* parser)
{
	switch (parser->current.type) {
		case TT(If):
			return scirpt_parser_parse_if(parser);
		case TT(Loop):
			return scirpt_parser_parse_loop(parser);
		case TT(While):
			return scirpt_parser_parse_while(parser);
		case TT(For):
			return scirpt_parser_parse_for(parser);
		case TT(Let):
			return scirpt_parser_parse_let(parser);
		case TT(Fn):
			return scirpt_parser_parse_function(parser);
		case TT(Return):
			return scirpt_parser_parse_return(parser);
		case TT(Break):
			return scirpt_parser_parse_break(parser);
		default:
			return scirpt_parser_parse_assign(parser);
	}
}

static inline ScirptExpr* assign(
	ScirptExprAssignType type,
	ScirptExpr* subject,
	ScirptExpr* value,
	ScirptPosition pos
)
{
	return alloc_expr((ScirptExpr) {
		.type = AET(Assign),
		.pos = pos,
		.assign = (ScirptExprAssign) {
			.type = type,
			.subject = subject,
			.value = value,
		},
	});
}

ScirptExpr* scirpt_parser_parse_assign(ScirptParser* parser)
{
	ScirptPosition pos = parser->current.pos;
	ScirptExpr* subject = scirpt_parser_parse_expr(parser);
	if (current_is(parser, TT(Equal))) {
		step(parser);
		ScirptExpr* value = scirpt_parser_parse_expr(parser);
		return assign(ScirptExprAssignTypeEqual, subject, value, pos);
	} else if (current_is(parser, TT(PlusEqual))) {
		step(parser);
		ScirptExpr* value = scirpt_parser_parse_expr(parser);
		return assign(ScirptExprAssignTypeAdd, subject, value, pos);
	} else if (current_is(parser, TT(MinusEqual))) {
		step(parser);
		ScirptExpr* value = scirpt_parser_parse_expr(parser);
		return assign(ScirptExprAssignTypeSubtract, subject, value, pos);
	} else if (current_is(parser, TT(AsteriskEqual))) {
		step(parser);
		ScirptExpr* value = scirpt_parser_parse_expr(parser);
		return assign(ScirptExprAssignTypeMultiply, subject, value, pos);
	} else if (current_is(parser, TT(SlashEqual))) {
		step(parser);
		ScirptExpr* value = scirpt_parser_parse_expr(parser);
		return assign(ScirptExprAssignTypeDivide, subject, value, pos);
	} else if (current_is(parser, TT(PercentEqual))) {
		step(parser);
		ScirptExpr* value = scirpt_parser_parse_expr(parser);
		return assign(ScirptExprAssignTypeModulo, subject, value, pos);
	} else if (current_is(parser, TT(AsteriskAsteriskEqual))) {
		step(parser);
		ScirptExpr* value = scirpt_parser_parse_expr(parser);
		return assign(ScirptExprAssignTypeExponent, subject, value, pos);
	} else {
		return subject;
	}
}

ScirptExpr* scirpt_parser_parse_expr(ScirptParser* parser)
{
	return scirpt_parser_parse_or(parser);
}

static inline ScirptExpr* binary(
	ScirptExprBinaryType type,
	ScirptExpr* left,
	ScirptExpr* right,
	ScirptPosition pos
)
{
	return alloc_expr((ScirptExpr) {
		.type = AET(Binary),
		.pos = pos,
		.binary = (ScirptExprBinary) {
			.type = type,
			.left = left,
			.right = right,
		},
	});
}

ScirptExpr* scirpt_parser_parse_or(ScirptParser* parser)
{
	ScirptPosition pos = parser->current.pos;
	ScirptExpr* left = scirpt_parser_parse_and(parser);
	while (!done(parser)) {
		if (current_is(parser, TT(Or))) {
			step(parser);
			ScirptExpr* right = scirpt_parser_parse_and(parser);
			left = binary(ScirptExprBinaryTypeOr, left, right, pos);
		} else {
			break;
		}
	}
	return left;
}

ScirptExpr* scirpt_parser_parse_and(ScirptParser* parser)
{
	ScirptPosition pos = parser->current.pos;
	ScirptExpr* left = scirpt_parser_parse_equality(parser);
	while (!done(parser)) {
		if (current_is(parser, TT(And))) {
			step(parser);
			ScirptExpr* right = scirpt_parser_parse_equality(parser);
			left = binary(ScirptExprBinaryTypeAnd, left, right, pos);
		} else {
			break;
		}
	}
	return left;
}

ScirptExpr* scirpt_parser_parse_equality(ScirptParser* parser)
{
	ScirptPosition pos = parser->current.pos;
	ScirptExpr* left = scirpt_parser_parse_comparison(parser);
	while (!done(parser)) {
		if (current_is(parser, TT(EqualEqual))) {
			step(parser);
			ScirptExpr* right = scirpt_parser_parse_comparison(parser);
			left = binary(ScirptExprBinaryTypeEqual, left, right, pos);
		} else if (current_is(parser, TT(ExclamationEqual))) {
			step(parser);
			ScirptExpr* right = scirpt_parser_parse_comparison(parser);
			left = binary(ScirptExprBinaryTypeInequal, left, right, pos);
		} else {
			break;
		}
	}
	return left;
}

ScirptExpr* scirpt_parser_parse_comparison(ScirptParser* parser)
{
	ScirptPosition pos = parser->current.pos;
	ScirptExpr* left = scirpt_parser_parse_add_subtract(parser);
	if (current_is(parser, TT(Lt))) {
		step(parser);
		ScirptExpr* right = scirpt_parser_parse_add_subtract(parser);
		return binary(ScirptExprBinaryTypeLt, left, right, pos);
	} else if (current_is(parser, TT(Gt))) {
		step(parser);
		ScirptExpr* right = scirpt_parser_parse_add_subtract(parser);
		return binary(ScirptExprBinaryTypeGt, left, right, pos);
	} else if (current_is(parser, TT(LtEqual))) {
		step(parser);
		ScirptExpr* right = scirpt_parser_parse_add_subtract(parser);
		return binary(ScirptExprBinaryTypeLtEqual, left, right, pos);
	} else if (current_is(parser, TT(GtEqual))) {
		step(parser);
		ScirptExpr* right = scirpt_parser_parse_add_subtract(parser);
		return binary(ScirptExprBinaryTypeGtEqual, left, right, pos);
	} else if (current_is(parser, TT(In))) {
		step(parser);
		ScirptExpr* right = scirpt_parser_parse_add_subtract(parser);
		return binary(ScirptExprBinaryTypeIn, left, right, pos);
	} else {
		return left;
	}
}

ScirptExpr* scirpt_parser_parse_add_subtract(ScirptParser* parser)
{
	ScirptPosition pos = parser->current.pos;
	ScirptExpr* left = scirpt_parser_parse_multiply_divide_modulo(parser);
	while (!done(parser)) {
		if (current_is(parser, TT(Plus))) {
			step(parser);
			ScirptExpr* right
				= scirpt_parser_parse_multiply_divide_modulo(parser);
			left = binary(ScirptExprBinaryTypeAdd, left, right, pos);
		} else if (current_is(parser, TT(Minus))) {
			step(parser);
			ScirptExpr* right
				= scirpt_parser_parse_multiply_divide_modulo(parser);
			left = binary(ScirptExprBinaryTypeSubtract, left, right, pos);
		} else {
			break;
		}
	}
	return left;
}

ScirptExpr* scirpt_parser_parse_multiply_divide_modulo(ScirptParser* parser)
{
	ScirptPosition pos = parser->current.pos;
	ScirptExpr* left = scirpt_parser_parse_negate(parser);
	while (!done(parser)) {
		if (current_is(parser, TT(Asterisk))) {
			step(parser);
			ScirptExpr* right = scirpt_parser_parse_negate(parser);
			left = binary(ScirptExprBinaryTypeMultiply, left, right, pos);
		} else if (current_is(parser, TT(Slash))) {
			step(parser);
			ScirptExpr* right = scirpt_parser_parse_negate(parser);
			left = binary(ScirptExprBinaryTypeDivide, left, right, pos);
		} else if (current_is(parser, TT(Percent))) {
			step(parser);
			ScirptExpr* right = scirpt_parser_parse_negate(parser);
			left = binary(ScirptExprBinaryTypeModulo, left, right, pos);
		} else {
			break;
		}
	}
	return left;
}

static inline ScirptExpr*
unary(ScirptExprUnaryType type, ScirptExpr* subject, ScirptPosition pos)
{
	return alloc_expr((ScirptExpr) {
		.type = AET(Unary),
		.pos = pos,
		.unary = (ScirptExprUnary) {
			.type = type,
			.subject = subject,
		}, 
	});
}

ScirptExpr* scirpt_parser_parse_negate(ScirptParser* parser)
{
	if (current_is(parser, TT(Minus))) {
		ScirptPosition pos = parser->current.pos;
		step(parser);
		ScirptExpr* subject = scirpt_parser_parse_negate(parser);
		return unary(ScirptExprUnaryTypeNegate, subject, pos);
	} else {
		return scirpt_parser_parse_exponent(parser);
	}
}

ScirptExpr* scirpt_parser_parse_exponent(ScirptParser* parser)
{
	ScirptPosition pos = parser->current.pos;
	ScirptExpr* left = scirpt_parser_parse_unary(parser);
	while (!done(parser)) {
		if (current_is(parser, TT(AsteriskAsterisk))) {
			step(parser);
			ScirptExpr* right = scirpt_parser_parse_unary(parser);
			left = binary(ScirptExprBinaryTypeExponent, left, right, pos);
		} else {
			break;
		}
	}
	return left;
}

ScirptExpr* scirpt_parser_parse_unary(ScirptParser* parser)
{
	ScirptPosition pos = parser->current.pos;
	if (current_is(parser, TT(Not))) {
		step(parser);
		ScirptExpr* subject = scirpt_parser_parse_unary(parser);
		return unary(ScirptExprUnaryTypeNot, subject, pos);
	} else {
		return scirpt_parser_parse_member_call_index_expr(parser);
	}
}

ScirptExpr* scirpt_parser_parse_member_call_index_expr(ScirptParser* parser)
{
	ScirptPosition pos = parser->current.pos;
	ScirptExpr* subject = scirpt_parser_parse_operand(parser);
	while (!done(parser)) {
		if (current_is(parser, TT(Dot))) {
			step(parser);
			if (!current_is(parser, TT(Id))) {
				error(
					parser,
					heapstring_from_cstring("expected id"),
					parser->current.pos
				);
				scirpt_expr_delete(subject);
				return alloc_expr((ScirptExpr) {
					.type = AET(Error),
					.pos = pos,
				});
			}
			HeapString value = heapstring_from(
				&parser->text[parser->current.pos.index], parser->current.length
			);
			subject = alloc_expr((ScirptExpr) {
				.type = AET(Member),
				.pos = pos,
				.member = (ScirptExprMember) {
					.subject = subject,
					.value = value,
				},
			});
		} else if (current_is(parser, TT(LParen))) {
			step(parser);
			ScirptExprArray args;
			scirpt_expr_array_construct(&args);
			if (!done(parser) && parser->current.type != TT(RParen)) {
				ScirptExpr* value = scirpt_parser_parse_operand(parser);
				scirpt_expr_array_append(&args, value);
				while (current_is(parser, TT(Comma))) {
					step(parser);
					if (done(parser) || parser->current.type == TT(RParen))
						break;
					ScirptExpr* value = scirpt_parser_parse_operand(parser);
					scirpt_expr_array_append(&args, value);
				}
			}
			if (!current_is(parser, TT(RParen))) {
				error(
					parser,
					heapstring_from_cstring("expected ')'"),
					parser->current.pos
				);
				scirpt_expr_delete(subject);
				return alloc_expr((ScirptExpr) {
					.type = AET(Error),
					.pos = pos,
				});
			}
			step(parser);
			subject = alloc_expr((ScirptExpr) {
				.type = AET(Call),
				.pos = pos,
				.call = (ScirptExprCall) {
					.subject = subject,
					.args = args,
				},
			});
		} else if (current_is(parser, TT(LBracket))) {
			step(parser);
			ScirptExpr* value = scirpt_parser_parse_operand(parser);
			if (!current_is(parser, TT(RBracket))) {
				error(
					parser,
					heapstring_from_cstring("expected ']'"),
					parser->current.pos
				);
				scirpt_expr_delete(subject);
				return alloc_expr((ScirptExpr) {
					.type = AET(Error),
					.pos = pos,
				});
			}
			step(parser);
			subject = alloc_expr((ScirptExpr) {
				.type = AET(Index),
				.pos = pos,
				.index = (ScirptExprIndex) {
					.subject = subject,
					.value = value,
				},
			});
		} else {
			break;
		}
	}
	return subject;
}

ScirptExpr* scirpt_parser_parse_operand(ScirptParser* parser)
{
	ScirptPosition pos = parser->current.pos;
	switch (parser->current.type) {
		case TT(Id): {
			HeapString value = heapstring_from(
				&parser->text[parser->current.pos.index], parser->current.length
			);
			step(parser);
			return alloc_expr((ScirptExpr) {
				.type = AET(Id),
				.pos = pos,
				.id_value = value,
			});
		}
		case TT(Int):
			return scirpt_parser_parse_int_or_float(parser);
		case TT(String):
			return scirpt_parser_parse_string(parser);
		case TT(Null):
			return step_alloc_expr(
				parser,
				(ScirptExpr) {
					.type = AET(Null),
					.pos = pos,
				}
			);
		case TT(False):
			return step_alloc_expr(
				parser,
				(ScirptExpr) {
					.type = AET(Bool),
					.pos = pos,
					.bool_value = false,
				}
			);
		case TT(True):
			return step_alloc_expr(
				parser,
				(ScirptExpr) {
					.type = AET(Bool),
					.pos = pos,
					.bool_value = true,
				}
			);
		case TT(LParen):
			return scirpt_parser_parse_group(parser);
		case TT(Exclamation):
			return scirpt_parser_parse_object(parser);
		case TT(LBracket):
			return scirpt_parser_parse_array(parser);
		case TT(LBrace):
			return scirpt_parser_parse_block(parser);
		case TT(If):
			return scirpt_parser_parse_if(parser);
		case TT(Loop):
			return scirpt_parser_parse_loop(parser);
		case TT(While):
			return scirpt_parser_parse_while(parser);
		case TT(For):
			return scirpt_parser_parse_for(parser);
		case TT(Fn):
			return scirpt_parser_parse_lambda(parser);
		case TT(Eof): {
			error(
				parser,
				heapstring_from_cstring("expected value, got Eof"),
				parser->current.pos
			);
			return alloc_expr((ScirptExpr) {
				.type = AET(Error),
				.pos = pos,
			});
		}
		case TT(InvalidChar): {
			error(
				parser,
				heapstring_from_cstring("invalid char"),
				parser->current.pos
			);
			step(parser);
			return alloc_expr((ScirptExpr) {
				.type = AET(Error),
				.pos = pos,
			});
		}
		case TT(MalformedComment): {
			error(
				parser,
				heapstring_from_cstring("malformed comment"),
				parser->current.pos
			);
			return alloc_expr((ScirptExpr) {
				.type = AET(Error),
				.pos = pos,
			});
		}
		case TT(MalformedString): {
			error(
				parser,
				heapstring_from_cstring("malformed string"),
				parser->current.pos
			);
			step(parser);
			return alloc_expr((ScirptExpr) {
				.type = AET(Error),
				.pos = pos,
			});
		}
		default: {
			fprintf(stderr, "unhandled token %d\n", parser->current.type);
			error(
				parser,
				heapstring_from_cstring("expected value"),
				parser->current.pos
			);
			step(parser);
			return alloc_expr((ScirptExpr) {
				.type = AET(Error),
				.pos = pos,
			});
		}
	}
}

ScirptExpr* scirpt_parser_parse_int_or_float(ScirptParser* parser)
{
	ScirptPosition pos = parser->current.pos;
	HeapString int_string = heapstring_from(
		&parser->text[parser->current.pos.index], parser->current.length
	);
	step(parser);
	if (current_is(parser, TT(Decimals))) {
		HeapString float_string = heapstring_from(
			&parser->text[parser->current.pos.index], parser->current.length
		);
		step(parser);
		StringBuilder builder;
		stringbuilder_construct(&builder);
		for (size_t i = 0; i < int_string.length; ++i)
			stringbuilder_append(&builder, int_string.data[i]);
		for (size_t i = 0; i < float_string.length; ++i)
			stringbuilder_append(&builder, float_string.data[i]);
		heapstring_destroy(&int_string);
		heapstring_destroy(&float_string);
		HeapString value_string = stringbuilder_build(&builder);
		stringbuilder_destroy(&builder);
		double value = atof(value_string.data);
		heapstring_destroy(&value_string);
		return alloc_expr((ScirptExpr) {
			.type = AET(Float),
			.pos = pos,
			.float_value = value,
		});
	} else {
		int64_t value = atoll(int_string.data);
		heapstring_destroy(&int_string);
		return alloc_expr((ScirptExpr) {
			.type = AET(Int),
			.pos = pos,
			.int_value = value,
		});
	}
}

ScirptExpr* scirpt_parser_parse_string(ScirptParser* parser)
{
	ScirptPosition pos = parser->current.pos;
	UnescapeStringResult result = common_unescape_string((StringView) {
		.data = &parser->text[parser->current.pos.index],
		.length = parser->current.length - 2,
	});
	if (!result.ok) {
		error(parser, result.error, parser->current.pos);
		step(parser);
		return alloc_expr((ScirptExpr) {
			.type = AET(Error),
		});
	}
	HeapString value = result.value;
	step(parser);
	return alloc_expr((ScirptExpr) {
		.type = AET(String),
		.pos = pos,
		.string_value = value,
	});
}

ScirptExpr* scirpt_parser_parse_group(ScirptParser* parser)
{
	step(parser);
	ScirptExpr* expr = scirpt_parser_parse_expr(parser);
	if (!current_is(parser, TT(RParen))) {
		error(
			parser, heapstring_from_cstring("expected ')'"), parser->current.pos
		);
		scirpt_expr_delete(expr);
		return alloc_expr((ScirptExpr) { .type = AET(Error) });
	}
	step(parser);
	return expr;
}

ScirptExpr* scirpt_parser_parse_array(ScirptParser* parser)
{
	ScirptPosition pos = parser->current.pos;
	step(parser);
	ScirptExprArray values;
	scirpt_expr_array_construct(&values);
	if (!done(parser) && parser->current.type != TT(RBracket)) {
		ScirptExpr* value = scirpt_parser_parse_expr(parser);
		scirpt_expr_array_append(&values, value);
		while (current_is(parser, TT(Comma))) {
			step(parser);
			if (done(parser) || parser->current.type == TT(RBracket))
				break;
			ScirptExpr* value = scirpt_parser_parse_expr(parser);
			scirpt_expr_array_append(&values, value);
		}
	}
	if (!current_is(parser, TT(RBracket))) {
		error(
			parser, heapstring_from_cstring("expected ']'"), parser->current.pos
		);
		scirpt_expr_array_clean_and_destroy(&values);
		return alloc_expr((ScirptExpr) { .type = AET(Error) });
	}
	step(parser);
	return alloc_expr((ScirptExpr) {
		.type = AET(Array),
		.pos = pos,
		.array_value = values,
	});
}

RESULT_WITH_CTORS(
	HeapString, HeapString, IdOrStringValueResult, id_or_string_value_result
)

static inline IdOrStringValueResult
id_or_string_value(const ScirptParser* parser)
{
	if (parser->current.type == TT(Id)) {
		return id_or_string_value_result_ok(heapstring_from(
			&parser->text[parser->current.pos.index], parser->current.length
		));
	} else if (parser->current.type == TT(String)) {
		UnescapeStringResult result = common_unescape_string((StringView) {
			.data = &parser->text[parser->current.pos.index],
			.length = parser->current.length - 2,
		});
		if (result.ok) {
			return id_or_string_value_result_ok(result.value);
		} else {
			return id_or_string_value_result_error(result.error);
		}
	} else {
		return id_or_string_value_result_error(
			heapstring_from_cstring("expected Id or String")
		);
	}
}

RESULT_WITH_CTORS(
	ScirptExprObjectEntry,
	HeapString,
	ParseObjectEntryResult,
	parse_object_entry_result
)

static inline ParseObjectEntryResult parse_object_entry(ScirptParser* parser)
{

	IdOrStringValueResult key_result = id_or_string_value(parser);
	if (!key_result.ok) {
		error(parser, heapstring_clone(&key_result.error), parser->current.pos);
		return parse_object_entry_result_error(key_result.error);
	}
	step(parser);
	if (!current_is(parser, TT(Colon))) {
		return parse_object_entry_result_error(
			heapstring_from_cstring("expected ':'")
		);
	}
	step(parser);
	ScirptExpr* value = scirpt_parser_parse_expr(parser);
	return parse_object_entry_result_ok((ScirptExprObjectEntry) {
		.key = key_result.value,
		.value = value,
	});
}

ScirptExpr* scirpt_parser_parse_object(ScirptParser* parser)
{
	ScirptPosition pos = parser->current.pos;
	step(parser);
	if (!current_is(parser, TT(LBrace))) {
		error(
			parser, heapstring_from_cstring("expected '{'"), parser->current.pos
		);
		return alloc_expr((ScirptExpr) { .type = AET(Error) });
	}
	step(parser);
	ScirptExprObjectEntryArray entries;
	scirpt_expr_object_entry_array_construct(&entries);
	if (!done(parser) && parser->current.type != TT(RBrace)) {
		ParseObjectEntryResult entry_result = parse_object_entry(parser);
		if (!entry_result.ok) {
			error(parser, entry_result.error, parser->current.pos);
			scirpt_expr_object_entry_array_destroy(&entries);
			return alloc_expr((ScirptExpr) { .type = AET(Error) });
		}
		scirpt_expr_object_entry_array_append(&entries, entry_result.value);
		while (current_is(parser, TT(Comma))) {
			step(parser);
			if (done(parser) || parser->current.type == TT(RBrace))
				break;
			ParseObjectEntryResult entry_result = parse_object_entry(parser);
			if (!entry_result.ok) {
				error(parser, entry_result.error, parser->current.pos);
				for (size_t i = 0; i < entries.length; ++i) {
					heapstring_destroy(&entries.data[i].key);
					scirpt_expr_delete(entries.data[i].value);
				}
				scirpt_expr_object_entry_array_destroy(&entries);
				return alloc_expr((ScirptExpr) { .type = AET(Error) });
			}
			scirpt_expr_object_entry_array_append(&entries, entry_result.value);
		}
	}
	if (!current_is(parser, TT(RBrace))) {
		error(
			parser, heapstring_from_cstring("expected '}'"), parser->current.pos
		);
		return alloc_expr((ScirptExpr) { .type = AET(Error) });
	}
	step(parser);
	return alloc_expr((ScirptExpr) {
		.type = AET(Object),
		.pos = pos,
		.object = (ScirptExprObject) {
			.entries = entries,
		},
	});
}

static inline bool requires_semicolon(ScirptExprType type)
{
	switch (type) {
		case AET(Let):
			return true;
		default:
			return false;
	}
}

ScirptExpr* scirpt_parser_parse_block(ScirptParser* parser)
{
	ScirptPosition pos = parser->current.pos;
	step(parser);
	ScirptExprArray statements;
	scirpt_expr_array_construct(&statements);
	while (!done(parser) && parser->current.type != TT(RBrace)) {
		ScirptExpr* statement = scirpt_parser_parse_statement(parser);
		scirpt_expr_array_append(&statements, statement);
		if (current_is(parser, TT(Semicolon))) {
			step(parser);
			while (current_is(parser, TT(Semicolon)))
				step(parser);
		} else {
			if (requires_semicolon(statement->type)) {
				error(
					parser,
					heapstring_from_cstring(
						"expected ';', required for statement"
					),
					parser->current.pos
				);
			}
			break;
		}
	}
	if (!current_is(parser, TT(RBrace))) {
		error(
			parser, heapstring_from_cstring("expected '}'"), parser->current.pos
		);
		for (size_t i = 0; i < statements.length; ++i)
			scirpt_expr_delete(statements.data[i]);
		scirpt_expr_array_destroy(&statements);
		return alloc_expr((ScirptExpr) {
			.type = AET(Error),
			.pos = pos,
		});
	}
	step(parser);
	return alloc_expr((ScirptExpr){
		.type = AET(Block),
		.pos = pos,
		.block = (ScirptExprBlock) {
			.statements = statements,
		},
	});
}

ScirptExpr* scirpt_parser_parse_if(ScirptParser* parser)
{
	ScirptPosition pos = parser->current.pos;
	step(parser);
	ScirptExpr* condition = scirpt_parser_parse_expr(parser);
	if (!current_is(parser, TT(LBrace))) {
		error(
			parser, heapstring_from_cstring("expected '{'"), parser->current.pos
		);
		scirpt_expr_delete(condition);
		return alloc_expr((ScirptExpr) {
			.type = AET(Error),
		});
	}
	ScirptExpr* truthy = scirpt_parser_parse_expr(parser);
	ScirptExpr* falsy = NULL;
	if (current_is(parser, TT(Else))) {
		step(parser);
		if (!current_is(parser, TT(LBrace))) {
			error(
				parser,
				heapstring_from_cstring("expected '{'"),
				parser->current.pos
			);
			scirpt_expr_delete(condition);
			scirpt_expr_delete(truthy);
		}
		falsy = scirpt_parser_parse_expr(parser);
	}
	return alloc_expr((ScirptExpr) {
		.type = AET(If),
		.pos = pos,
		.if_expr = (ScirptExprIf) {
			.condition = condition,
			.truthy = truthy,
			.falsy = falsy,
		},
	});
}

ScirptExpr* scirpt_parser_parse_loop(ScirptParser* parser)
{
	ScirptPosition pos = parser->current.pos;
	step(parser);
	if (!current_is(parser, TT(LBrace))) {
		error(
			parser, heapstring_from_cstring("expected '{'"), parser->current.pos
		);
		return alloc_expr((ScirptExpr) {
			.type = AET(Error),
		});
	}
	ScirptExpr* body = scirpt_parser_parse_expr(parser);
	return alloc_expr((ScirptExpr) {
		.type = AET(Loop),
		.pos = pos,
		.loop = (ScirptExprLoop) {
			.body = body,
		},
	});
}

ScirptExpr* scirpt_parser_parse_while(ScirptParser* parser)
{
	ScirptPosition pos = parser->current.pos;
	step(parser);
	ScirptExpr* condition = scirpt_parser_parse_expr(parser);
	if (!current_is(parser, TT(LBrace))) {
		error(
			parser, heapstring_from_cstring("expected '{'"), parser->current.pos
		);
		scirpt_expr_delete(condition);
		return alloc_expr((ScirptExpr) {
			.type = AET(Error),
		});
	}
	ScirptExpr* body = scirpt_parser_parse_expr(parser);
	return alloc_expr((ScirptExpr) {
		.type = AET(While),
		.pos = pos,
		.while_expr = (ScirptExprWhile) {
			.condition = condition,
			.body = body,
		},
	});
}

ScirptExpr* scirpt_parser_parse_for(ScirptParser* parser)
{
	ScirptPosition pos = parser->current.pos;
	step(parser);
	if (!current_is(parser, TT(Id))) {
		error(
			parser, heapstring_from_cstring("expected Id"), parser->current.pos
		);
		return alloc_expr((ScirptExpr) {
			.type = AET(Error),
		});
	}
	HeapString subject = heapstring_from(
		&parser->text[parser->current.pos.index], parser->current.length
	);
	step(parser);
	if (!current_is(parser, TT(In))) {
		error(
			parser,
			heapstring_from_cstring("expected 'in'"),
			parser->current.pos
		);
		heapstring_destroy(&subject);
		return alloc_expr((ScirptExpr) {
			.type = AET(Error),
		});
	}
	step(parser);
	ScirptExpr* value = scirpt_parser_parse_expr(parser);
	if (!current_is(parser, TT(LBrace))) {
		error(
			parser, heapstring_from_cstring("expected '{'"), parser->current.pos
		);
		heapstring_destroy(&subject);
		scirpt_expr_delete(value);
		return alloc_expr((ScirptExpr) {
			.type = AET(Error),
		});
	}
	ScirptExpr* body = scirpt_parser_parse_expr(parser);
	return alloc_expr((ScirptExpr) {
		.type = AET(While),
		.pos = pos,
		.while_expr = (ScirptExprWhile) {
			.condition = value,
			.body = body,
		},
	});
}

ScirptExpr* scirpt_parser_parse_lambda(ScirptParser* parser)
{
	ScirptPosition pos = parser->current.pos;
	step(parser);
	if (!current_is(parser, TT(LParen))) {
		error(
			parser, heapstring_from_cstring("expected '('"), parser->current.pos
		);
		return alloc_expr((ScirptExpr) {
			.type = AET(Error),
		});
	}
	step(parser);
	HeapStringArray params;
	heapstring_array_construct(&params);
	if (!done(parser) && parser->current.type == TT(RParen)) {
		step(parser);
		if (!current_is(parser, TT(Id))) {
			error(
				parser,
				heapstring_from_cstring("expected Id"),
				parser->current.pos
			);
			heapstring_array_destroy(&params);
			return alloc_expr((ScirptExpr) {
				.type = AET(Error),
			});
		}
		HeapString param = heapstring_from(
			&parser->text[parser->current.pos.index], parser->current.length
		);
		heapstring_array_append(&params, param);
		step(parser);
		while (current_is(parser, TT(Comma))) {
			step(parser);
			if (done(parser) || parser->current.type == TT(RParen))
				break;
			if (!current_is(parser, TT(Id))) {
				error(
					parser,
					heapstring_from_cstring("expected Id"),
					parser->current.pos
				);
				for (size_t i = 0; i < params.length; ++i)
					heapstring_destroy(&params.data[i]);
				heapstring_array_destroy(&params);
				return alloc_expr((ScirptExpr) {
					.type = AET(Error),
				});
			}
			HeapString param = heapstring_from(
				&parser->text[parser->current.pos.index], parser->current.length
			);
			heapstring_array_append(&params, param);
			step(parser);
		}
	}
	if (!current_is(parser, TT(RParen))) {
		error(
			parser, heapstring_from_cstring("expected ')'"), parser->current.pos
		);
		for (size_t i = 0; i < params.length; ++i)
			heapstring_destroy(&params.data[i]);
		heapstring_array_destroy(&params);
		return alloc_expr((ScirptExpr) {
			.type = AET(Error),
			.pos = pos,
		});
	}
	step(parser);
	if (!current_is(parser, TT(LBrace))) {
		error(
			parser, heapstring_from_cstring("expected '{'"), parser->current.pos
		);
		for (size_t i = 0; i < params.length; ++i)
			heapstring_destroy(&params.data[i]);
		heapstring_array_destroy(&params);
		return alloc_expr((ScirptExpr) {
			.type = AET(Error),
		});
	}
	ScirptExpr* body = scirpt_parser_parse_expr(parser);
	return alloc_expr((ScirptExpr) {
		.type = AET(Lambda),
		.pos = pos,
		.lambda = (ScirptExprLambda) {
			.params = params,
			.body = body,
		},
	});
}

ScirptExpr* scirpt_parser_parse_function(ScirptParser* parser)
{
	ScirptPosition pos = parser->current.pos;
	step(parser);
	if (!current_is(parser, TT(Id))) {
		error(
			parser, heapstring_from_cstring("expected Id"), parser->current.pos
		);
		return alloc_expr((ScirptExpr) {
			.type = AET(Error),
		});
	}
	HeapString subject = heapstring_from(
		&parser->text[parser->current.pos.index], parser->current.length
	);
	step(parser);
	if (!current_is(parser, TT(LParen))) {
		error(
			parser, heapstring_from_cstring("expected '('"), parser->current.pos
		);
		return alloc_expr((ScirptExpr) {
			.type = AET(Error),
		});
	}
	step(parser);
	HeapStringArray params;
	heapstring_array_construct(&params);
	if (!done(parser) && parser->current.type == TT(RParen)) {
		step(parser);
		if (!current_is(parser, TT(Id))) {
			error(
				parser,
				heapstring_from_cstring("expected Id"),
				parser->current.pos
			);
			heapstring_destroy(&subject);
			heapstring_array_destroy(&params);
			return alloc_expr((ScirptExpr) {
				.type = AET(Error),
			});
		}
		HeapString param = heapstring_from(
			&parser->text[parser->current.pos.index], parser->current.length
		);
		heapstring_array_append(&params, param);
		step(parser);
		while (current_is(parser, TT(Comma))) {
			step(parser);
			if (done(parser) || parser->current.type == TT(RParen))
				break;
			if (!current_is(parser, TT(Id))) {
				error(
					parser,
					heapstring_from_cstring("expected Id"),
					parser->current.pos
				);
				heapstring_destroy(&subject);
				for (size_t i = 0; i < params.length; ++i)
					heapstring_destroy(&params.data[i]);
				heapstring_array_destroy(&params);
				return alloc_expr((ScirptExpr) {
					.type = AET(Error),
				});
			}
			HeapString param = heapstring_from(
				&parser->text[parser->current.pos.index], parser->current.length
			);
			heapstring_array_append(&params, param);
			step(parser);
		}
	}
	if (!current_is(parser, TT(RParen))) {
		error(
			parser, heapstring_from_cstring("expected ')'"), parser->current.pos
		);
		heapstring_destroy(&subject);
		for (size_t i = 0; i < params.length; ++i)
			heapstring_destroy(&params.data[i]);
		heapstring_array_destroy(&params);
		return alloc_expr((ScirptExpr) {
			.type = AET(Error),
			.pos = pos,
		});
	}
	step(parser);
	if (!current_is(parser, TT(LBrace))) {
		error(
			parser, heapstring_from_cstring("expected '{'"), parser->current.pos
		);
		heapstring_destroy(&subject);
		for (size_t i = 0; i < params.length; ++i)
			heapstring_destroy(&params.data[i]);
		heapstring_array_destroy(&params);
		return alloc_expr((ScirptExpr) {
			.type = AET(Error),
		});
	}
	ScirptExpr* body = scirpt_parser_parse_expr(parser);
	return alloc_expr((ScirptExpr) {
		.type = AET(Function),
		.pos = pos,
		.function = (ScirptExprFunction) {
			.subject = subject,
			.params = params,
			.body = body,
		},
	});
}

ScirptExpr* scirpt_parser_parse_let(ScirptParser* parser)
{
	ScirptPosition pos = parser->current.pos;
	step(parser);
	if (!current_is(parser, TT(Id))) {
		error(
			parser, heapstring_from_cstring("expected Id"), parser->current.pos
		);
		return alloc_expr((ScirptExpr) {
			.type = AET(Error),
		});
	}
	HeapString subject = heapstring_from(
		&parser->text[parser->current.pos.index], parser->current.length
	);
	step(parser);
	if (!current_is(parser, TT(Equal))) {
		error(
			parser, heapstring_from_cstring("expected '='"), parser->current.pos
		);
		heapstring_destroy(&subject);
		return alloc_expr((ScirptExpr) {
			.type = AET(Error),
		});
	}
	step(parser);
	ScirptExpr* value = scirpt_parser_parse_expr(parser);
	return alloc_expr((ScirptExpr) {
		.type = AET(Let),
		.pos = pos,
		.let = (ScirptExprLet) {
			.subject = subject,
			.value = value,
		},
	});
}

ScirptExpr* scirpt_parser_parse_return(ScirptParser* parser)
{
	ScirptPosition pos = parser->current.pos;
	step(parser);
	ScirptExpr* value = NULL;
	if (!done(parser) && parser->current.type != TT(Semicolon)) {
		value = scirpt_parser_parse_expr(parser);
	}
	return alloc_expr((ScirptExpr) {
		.type = AET(Return),
		.pos = pos,
		.return_statement = (ScirptExprReturn) {
			.value = value,
		},
	});
}

ScirptExpr* scirpt_parser_parse_break(ScirptParser* parser)
{
	ScirptPosition pos = parser->current.pos;
	step(parser);
	ScirptExpr* value = NULL;
	if (!done(parser) && parser->current.type != TT(Semicolon)) {
		value = scirpt_parser_parse_expr(parser);
	}
	return alloc_expr((ScirptExpr) {
		.type = AET(Break),
		.pos = pos,
		.break_statement = (ScirptExprBreak) {
			.value = value,
		},
	});
}

void scirpt_parser_error(
	ScirptParser* parser, HeapString message, ScirptPosition pos
)
{
	if (parser->ok)
		parser->ok = false;
	scirpt_parser_error_array_append(
		&parser->errors,
		(ScirptParserError) {
			.message = message,
			.pos = pos,
		}
	);
}

void scirpt_parser_step(ScirptParser* parser)
{
	parser->current = scirpt_lexer_next(parser->lexer);
}

bool scirpt_parser_current_is(const ScirptParser* parser, ScirptTokenType type)
{
	return parser->current.type == type;
}

bool scirpt_parser_done(const ScirptParser* parser)
{
	return parser->current.type == TT(Eof);
}