From 3332511290151d8282f86c715895331f1585e4c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20H=C3=B6lting?= <87192362+moritz-hoelting@users.noreply.github.com> Date: Thu, 29 Aug 2024 00:57:11 +0200 Subject: [PATCH] suggest similarly named functions if invoked function does not exist --- Cargo.toml | 2 ++ src/lib.rs | 3 ++ src/transpile/error.rs | 58 ++++++++++++++++++++++++++++++++++--- src/transpile/transpiler.rs | 41 ++++++++++++++------------ 4 files changed, 82 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cd636c5..545c0fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,10 +27,12 @@ colored = "2.1.0" derive_more = { version = "0.99.17", default-features = false, features = ["deref", "from", "deref_mut"] } enum-as-inner = "0.6.0" getset = "0.1.2" +itertools = "0.13.0" mlua = { version = "0.9.7", features = ["lua54", "vendored"], optional = true } path-absolutize = "3.1.1" 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" } +strsim = "0.11.1" strum = { version = "0.26.2", features = ["derive"] } strum_macros = "0.26.2" thiserror = "1.0.58" diff --git a/src/lib.rs b/src/lib.rs index 90fd7b6..ab696bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,6 +29,9 @@ use shulkerbox::{datapack::Datapack, virtual_fs::VFolder}; 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. /// /// # Errors diff --git a/src/transpile/error.rs b/src/transpile/error.rs index cd26669..2211359 100644 --- a/src/transpile/error.rs +++ b/src/transpile/error.rs @@ -1,6 +1,9 @@ //! Errors that can occur during transpilation. -use std::fmt::Display; +use std::{collections::HashMap, fmt::Display}; + +use getset::Getters; +use itertools::Itertools; use crate::{ base::{ @@ -10,6 +13,8 @@ use crate::{ syntax::syntax_tree::expression::Expression, }; +use super::transpiler::FunctionData; + /// Errors that can occur during transpilation. #[allow(clippy::module_name_repetitions, missing_docs)] #[derive(Debug, thiserror::Error, Clone)] @@ -28,9 +33,41 @@ pub enum TranspileError { pub type TranspileResult = Result; /// 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 span: Span, + #[get = "pub"] + span: Span, + #[get = "pub"] + alternatives: Vec, +} + +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::>(); + + Self { + alternatives, + span: identifier_span, + } + } } impl Display for MissingFunctionDeclaration { @@ -41,10 +78,23 @@ impl Display for MissingFunctionDeclaration { ); 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!( f, "\n{}", - SourceCodeDisplay::new(&self.span, Option::::None) + SourceCodeDisplay::new(&self.span, help_message.as_ref()) ) } } diff --git a/src/transpile/transpiler.rs b/src/transpile/transpiler.rs index 87015ac..923fa83 100644 --- a/src/transpile/transpiler.rs +++ b/src/transpile/transpiler.rs @@ -35,13 +35,13 @@ pub struct Transpiler { aliases: RwLock>, } -#[derive(Debug, Clone)] -struct FunctionData { - namespace: String, - identifier_span: Span, - statements: Vec, - public: bool, - annotations: HashMap>, +#[derive(Debug, Clone, PartialEq, Eq)] +pub(super) struct FunctionData { + pub(super) namespace: String, + pub(super) identifier_span: Span, + pub(super) statements: Vec, + pub(super) public: bool, + pub(super) annotations: HashMap>, } impl Transpiler { @@ -224,9 +224,10 @@ impl Transpiler { }) .ok_or_else(|| { let error = TranspileError::MissingFunctionDeclaration( - MissingFunctionDeclaration { - span: identifier_span.clone(), - }, + MissingFunctionDeclaration::from_context( + identifier_span.clone(), + &functions, + ), ); handler.receive(error.clone()); error @@ -244,10 +245,12 @@ impl Transpiler { .and_then(|q| functions.get(&q).filter(|f| f.public)) }) .ok_or_else(|| { - let error = - TranspileError::MissingFunctionDeclaration(MissingFunctionDeclaration { - span: identifier_span.clone(), - }); + let error = TranspileError::MissingFunctionDeclaration( + MissingFunctionDeclaration::from_context( + identifier_span.clone(), + &functions, + ), + ); handler.receive(error.clone()); error })?; @@ -297,10 +300,12 @@ impl Transpiler { .get(&program_query) .or_else(|| alias_query.and_then(|q| locations.get(&q).filter(|(_, p)| *p))) .ok_or_else(|| { - let error = - TranspileError::MissingFunctionDeclaration(MissingFunctionDeclaration { - span: identifier_span.clone(), - }); + let error = TranspileError::MissingFunctionDeclaration( + MissingFunctionDeclaration::from_context( + identifier_span.clone(), + &self.functions.read().unwrap(), + ), + ); handler.receive(error.clone()); error })