suggest similarly named functions if invoked function does not exist

This commit is contained in:
Moritz Hölting 2024-08-29 00:57:11 +02:00
parent 6422737cf3
commit 3332511290
4 changed files with 82 additions and 22 deletions

View File

@ -27,10 +27,12 @@ colored = "2.1.0"
derive_more = { version = "0.99.17", default-features = false, features = ["deref", "from", "deref_mut"] } derive_more = { version = "0.99.17", default-features = false, features = ["deref", "from", "deref_mut"] }
enum-as-inner = "0.6.0" enum-as-inner = "0.6.0"
getset = "0.1.2" getset = "0.1.2"
itertools = "0.13.0"
mlua = { version = "0.9.7", features = ["lua54", "vendored"], optional = true } mlua = { version = "0.9.7", features = ["lua54", "vendored"], optional = true }
path-absolutize = "3.1.1" path-absolutize = "3.1.1"
serde = { version = "1.0.197", features = ["derive", "rc"], optional = true } serde = { version = "1.0.197", features = ["derive", "rc"], optional = true }
shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", default-features = false, optional = true, rev = "a2d20dab8ea97bbd873edafb23afaad34292457f" } shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", default-features = false, optional = true, rev = "a2d20dab8ea97bbd873edafb23afaad34292457f" }
strsim = "0.11.1"
strum = { version = "0.26.2", features = ["derive"] } strum = { version = "0.26.2", features = ["derive"] }
strum_macros = "0.26.2" strum_macros = "0.26.2"
thiserror = "1.0.58" thiserror = "1.0.58"

View File

@ -29,6 +29,9 @@ use shulkerbox::{datapack::Datapack, virtual_fs::VFolder};
use crate::lexical::token_stream::TokenStream; use crate::lexical::token_stream::TokenStream;
/// The version of the `ShulkerScript` language.
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
/// Converts the given source code to tokens and returns a token stream. /// Converts the given source code to tokens and returns a token stream.
/// ///
/// # Errors /// # Errors

View File

@ -1,6 +1,9 @@
//! Errors that can occur during transpilation. //! Errors that can occur during transpilation.
use std::fmt::Display; use std::{collections::HashMap, fmt::Display};
use getset::Getters;
use itertools::Itertools;
use crate::{ use crate::{
base::{ base::{
@ -10,6 +13,8 @@ use crate::{
syntax::syntax_tree::expression::Expression, syntax::syntax_tree::expression::Expression,
}; };
use super::transpiler::FunctionData;
/// Errors that can occur during transpilation. /// Errors that can occur during transpilation.
#[allow(clippy::module_name_repetitions, missing_docs)] #[allow(clippy::module_name_repetitions, missing_docs)]
#[derive(Debug, thiserror::Error, Clone)] #[derive(Debug, thiserror::Error, Clone)]
@ -28,9 +33,41 @@ pub enum TranspileError {
pub type TranspileResult<T> = Result<T, TranspileError>; pub type TranspileResult<T> = Result<T, TranspileError>;
/// An error that occurs when a function declaration is missing. /// An error that occurs when a function declaration is missing.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Getters)]
pub struct MissingFunctionDeclaration { pub struct MissingFunctionDeclaration {
pub span: Span, #[get = "pub"]
span: Span,
#[get = "pub"]
alternatives: Vec<FunctionData>,
}
impl MissingFunctionDeclaration {
pub(super) fn from_context(
identifier_span: Span,
functions: &HashMap<(String, String), FunctionData>,
) -> Self {
let own_name = identifier_span.str();
let own_program_identifier = identifier_span.source_file().identifier();
let alternatives = functions
.iter()
.filter_map(|((program_identifier, function_name), data)| {
let normalized_distance = strsim::normalized_levenshtein(own_name, function_name);
(program_identifier == own_program_identifier
&& (normalized_distance > 0.8
|| strsim::levenshtein(own_name, function_name) < 3))
.then_some((normalized_distance, data))
})
.sorted_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal))
.map(|(_, data)| data)
.take(8)
.cloned()
.collect::<Vec<_>>();
Self {
alternatives,
span: identifier_span,
}
}
} }
impl Display for MissingFunctionDeclaration { impl Display for MissingFunctionDeclaration {
@ -41,10 +78,23 @@ impl Display for MissingFunctionDeclaration {
); );
write!(f, "{}", Message::new(Severity::Error, message))?; write!(f, "{}", Message::new(Severity::Error, message))?;
let help_message = if self.alternatives.is_empty() {
None
} else {
let mut message = String::from("did you mean ");
for (i, alternative) in self.alternatives.iter().enumerate() {
if i > 0 {
message.push_str(", ");
}
message.push_str(&format!("`{}`", alternative.identifier_span.str()));
}
Some(message + "?")
};
write!( write!(
f, f,
"\n{}", "\n{}",
SourceCodeDisplay::new(&self.span, Option::<u8>::None) SourceCodeDisplay::new(&self.span, help_message.as_ref())
) )
} }
} }

View File

@ -35,13 +35,13 @@ pub struct Transpiler {
aliases: RwLock<HashMap<(String, String), (String, String)>>, aliases: RwLock<HashMap<(String, String), (String, String)>>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Eq)]
struct FunctionData { pub(super) struct FunctionData {
namespace: String, pub(super) namespace: String,
identifier_span: Span, pub(super) identifier_span: Span,
statements: Vec<Statement>, pub(super) statements: Vec<Statement>,
public: bool, pub(super) public: bool,
annotations: HashMap<String, Option<String>>, pub(super) annotations: HashMap<String, Option<String>>,
} }
impl Transpiler { impl Transpiler {
@ -224,9 +224,10 @@ impl Transpiler {
}) })
.ok_or_else(|| { .ok_or_else(|| {
let error = TranspileError::MissingFunctionDeclaration( let error = TranspileError::MissingFunctionDeclaration(
MissingFunctionDeclaration { MissingFunctionDeclaration::from_context(
span: identifier_span.clone(), identifier_span.clone(),
}, &functions,
),
); );
handler.receive(error.clone()); handler.receive(error.clone());
error error
@ -244,10 +245,12 @@ impl Transpiler {
.and_then(|q| functions.get(&q).filter(|f| f.public)) .and_then(|q| functions.get(&q).filter(|f| f.public))
}) })
.ok_or_else(|| { .ok_or_else(|| {
let error = let error = TranspileError::MissingFunctionDeclaration(
TranspileError::MissingFunctionDeclaration(MissingFunctionDeclaration { MissingFunctionDeclaration::from_context(
span: identifier_span.clone(), identifier_span.clone(),
}); &functions,
),
);
handler.receive(error.clone()); handler.receive(error.clone());
error error
})?; })?;
@ -297,10 +300,12 @@ impl Transpiler {
.get(&program_query) .get(&program_query)
.or_else(|| alias_query.and_then(|q| locations.get(&q).filter(|(_, p)| *p))) .or_else(|| alias_query.and_then(|q| locations.get(&q).filter(|(_, p)| *p)))
.ok_or_else(|| { .ok_or_else(|| {
let error = let error = TranspileError::MissingFunctionDeclaration(
TranspileError::MissingFunctionDeclaration(MissingFunctionDeclaration { MissingFunctionDeclaration::from_context(
span: identifier_span.clone(), identifier_span.clone(),
}); &self.functions.read().unwrap(),
),
);
handler.receive(error.clone()); handler.receive(error.clone());
error error
}) })