lemonlang 0.0.3

an experimental, modern, purely safe, programming language.
use crate::{
	ast::Operator,
	diag::{self, Diag, Severity},
	range::Range,
};

// todo: improve this...
#[derive(Debug)]
pub enum SyntaxErr<'tce> {
	// type errors
	TypeMismatch { expected: String, found: String, range: Range },
	NotFn { found: String, range: Range },
	UnsupportedOperator { left: String, right: String, operator: &'tce Operator },
	ValueExpected { value: String, range: Range },
	UnexpectedValue { value: String, range: Range },
	BoundsError { value: String, found: String, range: Range },
	ArgsMismatch { expected: usize, found: usize, range: Range },

	// fn errors
	RedefineFnInSameScope { name: String, range: Range },
	ReturnLocalBorrow { range: Range },

	// context errors
	ReturnOutsideFn { range: Range },
	ReturnNotInFnScope { range: Range },
	RequiredTypeNotation { range: Range },
	ConnotReturnLocalRerefence { range: Range },

	// borrow errors
	Immutable { name: &'tce str, range: Range },
	BorrowConflict { range: Range },
	DoubleMutBorrow { range: Range },
	InvalidBorrow { range: Range },
	BorrowedValueDropped { range: Range },
	CannotDereference { type_name: String, range: Range },
	CannotBorrowAsMutable { name: String, range: Range },
	CannotBorrowAsMutableMoreThanOnce { name: String, range: Range },
	BorrowExpected { range: Range },
	CannotAssignImmutable { name: String, range: Range },

	// other errors
	NotFoundValue { name: &'tce str, range: Range },
	NotFoundModule { name: &'tce str, range: Range },
	InvalidFloat { range: Range },
	NumberTooLarge { range: Range },
	ExpectedNumber { range: Range },
	LeftHandCannotBeAssigned { range: Range },
	// const errors
	ConstOutsideGlobalScope { range: Range },
	ConstRedefinition { range: Range },
	// type alias / struct / enum...  errors
	NotFoundType { name: &'tce str, range: Range },

	// instaced impl
	ExpectInstacedType { found: String, range: Range },
	NotFoundField { name: &'tce str, range: Range },
	NotImpl { found: String, range: Range },

	// member and associated fn errors
	NotFoundMethodNamed { name: String, found: String, range: Range },
	NotFoundAssociateField { name: String, found: String, range: Range },
	// module errors
	// InvalidModulePath { path: String, range: Range },
	// ModuleRedefined { name: String, range: Range },
	// ModuleImportFailed { name: String, range: Range },
}

impl<'tce> SyntaxErr<'tce> {
	pub fn const_outside_global_scope(range: Range) -> Diag {
		Self::ConstOutsideGlobalScope { range }.into()
	}

	pub fn const_redefinition(range: Range) -> Diag {
		Self::ConstRedefinition { range }.into()
	}

	pub fn immutable(name: &'tce str, range: Range) -> Diag {
		Self::Immutable { name, range }.into()
	}

	pub fn type_mismatch(expected: String, found: String, range: Range) -> Diag {
		Self::TypeMismatch { expected, found, range }.into()
	}

	pub fn args_mismatch(expected: usize, found: usize, range: Range) -> Diag {
		Self::ArgsMismatch { expected, found, range }.into()
	}

	pub fn not_found_value(name: &'tce str, range: Range) -> Diag {
		Self::NotFoundValue { name, range }.into()
	}

	pub fn bounds_error(value: String, found: String, range: Range) -> Diag {
		Self::BoundsError { value, found, range }.into()
	}

	pub fn required_type_notation(range: Range) -> Diag {
		Self::RequiredTypeNotation { range }.into()
	}

	pub fn value_expected(value: String, range: Range) -> Diag {
		Self::ValueExpected { value, range }.into()
	}

	pub fn unexpected_value(value: String, range: Range) -> Diag {
		Self::UnexpectedValue { value, range }.into()
	}

	pub fn return_outside_fn(range: Range) -> Diag {
		Self::ReturnOutsideFn { range }.into()
	}

	pub fn invalid_float(range: Range) -> Diag {
		Self::InvalidFloat { range }.into()
	}

	pub fn number_too_large(range: Range) -> Diag {
		Self::NumberTooLarge { range }.into()
	}

	pub fn expected_number(range: Range) -> Diag {
		Self::ExpectedNumber { range }.into()
	}

	pub fn return_not_in_fn_scope(range: Range) -> Diag {
		Self::ReturnNotInFnScope { range }.into()
	}
	pub fn not_found_module(name: &'tce str, range: Range) -> Diag {
		Self::NotFoundModule { name, range }.into()
	}

	pub fn cannot_dereference(type_name: String, range: Range) -> Diag {
		Self::CannotDereference { type_name, range }.into()
	}
	pub fn not_a_fn(found: String, range: Range) -> Diag {
		Self::NotFn { found, range }.into()
	}

	pub fn borrow_conflict(range: Range) -> Diag {
		Self::BorrowConflict { range }.into()
	}

	pub fn double_mut_borrow(range: Range) -> Diag {
		Self::DoubleMutBorrow { range }.into()
	}

	pub fn invalid_borrow(range: Range) -> Diag {
		Self::InvalidBorrow { range }.into()
	}

	pub fn borrowed_value_dropped(range: Range) -> Diag {
		Self::BorrowedValueDropped { range }.into()
	}

	pub fn cannot_borrow_as_mutable(name: &str, range: Range) -> Diag {
		Self::CannotBorrowAsMutable { name: name.to_string(), range }.into()
	}

	pub fn cannot_borrow_as_mutable_more_than_once(name: &str, range: Range) -> Diag {
		Self::CannotBorrowAsMutableMoreThanOnce { name: name.to_string(), range }.into()
	}
	pub fn connot_return_local_rerefence(range: Range) -> Diag {
		Self::ConnotReturnLocalRerefence { range }.into()
	}
	pub fn unsupported_operator(left: String, right: String, operator: &'tce Operator) -> Diag {
		Self::UnsupportedOperator { left, right, operator }.into()
	}

	pub fn redefine_fn_in_same_scope(name: &str, range: Range) -> Diag {
		Self::RedefineFnInSameScope { name: name.to_string(), range }.into()
	}

	pub fn return_local_borrow(range: Range) -> Diag {
		Self::ReturnLocalBorrow { range }.into()
	}

	pub fn borrow_expected(range: Range) -> Diag {
		Self::BorrowExpected { range }.into()
	}

	pub fn cannot_assign_immutable(name: &str, range: Range) -> Diag {
		Self::CannotAssignImmutable { name: name.to_string(), range }.into()
	}

	pub fn not_found_type(name: &'tce str, range: Range) -> Diag {
		Self::NotFoundType { name, range }.into()
	}

	pub fn expect_instaced_type(found: String, range: Range) -> Diag {
		Self::ExpectInstacedType { found, range }.into()
	}

	pub fn not_found_field(name: &'tce str, range: Range) -> Diag {
		Self::NotFoundField { name, range }.into()
	}

	pub fn not_impl(found: String, range: Range) -> Diag {
		Self::NotImpl { found, range }.into()
	}

	pub fn not_found_method_named(name: String, found: String, range: Range) -> Diag {
		Self::NotFoundMethodNamed { name, found, range }.into()
	}
	pub fn not_found_associate_field(name: String, found: String, range: Range) -> Diag {
		Self::NotFoundAssociateField { name, found, range }.into()
	}

	pub fn left_hand_cannot_be_assigned(range: Range) -> Diag {
		Self::LeftHandCannotBeAssigned { range }.into()
	}
}
impl<'tce> From<SyntaxErr<'tce>> for diag::Diag {
	fn from(err: SyntaxErr<'tce>) -> Self {
		match err {
			SyntaxErr::TypeMismatch { expected, found, range } => {
				let text = format!("expected '{}', found '{}'", expected, found);
				diag::Diag::new(Severity::Err, text, range)
			}
			SyntaxErr::RequiredTypeNotation { range } => {
				let text = "required type notation, cannot infer type".to_string();
				diag::Diag::new(Severity::Err, text, range)
			}
			SyntaxErr::Immutable { name, range } => {
				let text = format!("variable '{}' is not mutable", name);
				diag::Diag::new(Severity::Err, text, range)
			}
			SyntaxErr::ArgsMismatch { expected, found, range } => {
				let text = format!("expected {} args, found {}", expected, found);
				diag::Diag::new(Severity::Err, text, range)
			}
			SyntaxErr::NotFoundValue { name, range } => {
				let text = format!("value '{}' not found", name);
				diag::Diag::new(Severity::Err, text, range)
			}
			SyntaxErr::BoundsError { value, found, range } => {
				let text = format!("'{}' out of bounds, expected '{}'", value, found);
				diag::Diag::new(Severity::Err, text, range)
			}
			SyntaxErr::UnsupportedOperator { left, right, operator } => {
				let text = format!("cannot {} '{}' to '{}'", operator.display(), left, right);
				diag::Diag::new(Severity::Err, text, operator.get_range())
			}
			SyntaxErr::ValueExpected { value, range } => {
				let text = format!("expected '{}', found UNIT", value);
				diag::Diag::new(Severity::Err, text, range)
			}
			SyntaxErr::UnexpectedValue { value, range } => {
				let text = format!("no expected value, found '{}'", value);
				diag::Diag::new(Severity::Err, text, range)
			}
			SyntaxErr::BorrowConflict { range } => {
				let text = "mutable and immutable borrows conflict.".to_string();
				diag::Diag::new(Severity::Err, text, range)
					.with_note("cannot borrow as mutable and immutable at the same time.")
			}
			SyntaxErr::DoubleMutBorrow { range } => {
				let text = "already mutably borrowed.".to_string();
				diag::Diag::new(Severity::Err, text, range)
			}
			SyntaxErr::InvalidBorrow { range } => {
				let text = "invalid borrow, value already freed.".to_string();
				diag::Diag::new(Severity::Err, text, range)
			}
			SyntaxErr::BorrowedValueDropped { range } => {
				let text = "borrowed value was dropped before borrow ended.".to_string();
				diag::Diag::new(Severity::Err, text, range)
			}
			SyntaxErr::InvalidFloat { range } => {
				let text = "floating-point number out of range".to_string();
				diag::Diag::new(Severity::Err, text, range)
			}
			SyntaxErr::NumberTooLarge { range } => {
				let text = "unsupport number".to_string();
				diag::Diag::new(Severity::Err, text, range)
			}
			SyntaxErr::ExpectedNumber { range } => {
				let text = "expected number".to_string();
				diag::Diag::new(Severity::Err, text, range)
			}
			SyntaxErr::ReturnNotInFnScope { range } => {
				let text = "return cannot be used in this context".to_string();
				diag::Diag::new(Severity::Err, text, range)
			}
			SyntaxErr::CannotDereference { type_name, range } => {
				let text = format!("'{}' cannot be dereferenced, expected a reference", type_name);
				diag::Diag::new(Severity::Err, text, range).with_note("try to use a reference instead")
			}
			SyntaxErr::ReturnOutsideFn { range } => {
				let text = "cannot return outside of a fn".to_string();
				diag::Diag::new(Severity::Err, text, range)
			}
			SyntaxErr::NotFn { found, range } => {
				let text = format!("expected a fn, found '{}'", found);
				diag::Diag::new(Severity::Err, text, range)
			}
			SyntaxErr::ConnotReturnLocalRerefence { range } => {
				let text = "cannot return a reference to a scoped value".to_string();
				diag::Diag::new(Severity::Err, text, range).with_note("try to return a value instead")
			}

			// const errors
			SyntaxErr::ConstOutsideGlobalScope { range } => {
				let text = "const can only be defined at global scope".to_string();
				diag::Diag::new(Severity::Err, text, range)
			}
			SyntaxErr::ConstRedefinition { range } => {
				let text = "const already defined".to_string();
				diag::Diag::new(Severity::Err, text, range)
			}
			SyntaxErr::NotFoundModule { name, range } => {
				let text = format!("module '{}' not found", name);
				diag::Diag::new(Severity::Err, text, range)
			}
			SyntaxErr::RedefineFnInSameScope { name, range } => {
				let text = format!("function '{}' is already defined in this scope", name);
				diag::Diag::new(Severity::Err, text, range).with_note("consider renaming the function")
			}
			SyntaxErr::ReturnLocalBorrow { range } => {
				let text = "cannot return a local borrow".to_string();
				diag::Diag::new(Severity::Err, text, range).with_note("try to return a value instead")
			}

			SyntaxErr::BorrowExpected { range } => {
				let text = "consider adding a borrow".to_string();
				diag::Diag::new(Severity::Err, text, range)
			}

			SyntaxErr::CannotBorrowAsMutableMoreThanOnce { name, range } => {
				let text = format!("cannot borrow '{}' as mutable more than once at a time", name);
				diag::Diag::new(Severity::Err, text, range)
			}
			SyntaxErr::CannotBorrowAsMutable { name, range } => {
				let text = format!("cannot borrow '{}' as mutable", name);
				diag::Diag::new(Severity::Err, text, range)
					.with_note(format!("consider change '{}' to mutable", name))
			}
			SyntaxErr::CannotAssignImmutable { name, range } => {
				let text = format!("cannot assign immutable '{}'", name);
				diag::Diag::new(Severity::Err, text, range).with_note("consider making it mutable")
			}
			SyntaxErr::NotFoundType { name, range } => {
				let text = format!("type '{}' not found in current scope", name);
				diag::Diag::new(Severity::Err, text, range)
			}
			SyntaxErr::ExpectInstacedType { found, range } => {
				let text = format!("expected `struct` or `enum`, found '{}'", found);
				diag::Diag::new(Severity::Err, text, range)
			}
			SyntaxErr::NotFoundField { name, range } => {
				let text = format!("field '{}' not found", name);
				diag::Diag::new(Severity::Err, text, range)
			}
			SyntaxErr::NotImpl { found, range } => {
				let text = format!("'{}' is not implemented", found);
				diag::Diag::new(Severity::Err, text, range).with_note("try to implement it, or instace it")
			}
			SyntaxErr::NotFoundMethodNamed { name, found, range } => {
				let text = format!("'{}' has no method named '{}'", found, name);
				diag::Diag::new(Severity::Err, text, range)
			}
			SyntaxErr::NotFoundAssociateField { name, found, range } => {
				let text = format!("'{}' has no associated field named '{}'", found, name);
				diag::Diag::new(Severity::Err, text, range)
			}
			SyntaxErr::LeftHandCannotBeAssigned { range } => {
				let text = "left-hand side can't be assigned".to_string();
				diag::Diag::new(Severity::Err, text, range)
			}
		}
	}
}