From 659683bd39d166974829267af53bfc30ce6c2e4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20H=C3=B6lting?= <87192362+moritz-hoelting@users.noreply.github.com> Date: Tue, 27 Aug 2024 22:55:26 +0200 Subject: [PATCH] add SourceCodeDisplay to MissingFunctionDeclaration error --- src/base/error.rs | 2 +- src/base/source_file.rs | 14 ++++-- src/lexical/token.rs | 3 +- src/lib.rs | 19 +++++--- src/syntax/error.rs | 2 +- src/transpile/error.rs | 38 +++++++++++++-- src/transpile/transpiler.rs | 97 ++++++++++++++++++++++--------------- tests/parsing/main.rs | 18 +++++-- 8 files changed, 134 insertions(+), 59 deletions(-) diff --git a/src/base/error.rs b/src/base/error.rs index 7a1a61b..b9051e5 100644 --- a/src/base/error.rs +++ b/src/base/error.rs @@ -10,7 +10,7 @@ pub enum Error { TokenizeError(#[from] crate::lexical::token::TokenizeError), #[error(transparent)] ParseError(#[from] crate::syntax::error::Error), - #[error("An error occurred while transpiling the source code.")] + #[error(transparent)] TranspileError(#[from] crate::transpile::TranspileError), #[error("An error occurred")] Other(&'static str), diff --git a/src/base/source_file.rs b/src/base/source_file.rs index 162d8a0..aec14c9 100644 --- a/src/base/source_file.rs +++ b/src/base/source_file.rs @@ -21,6 +21,9 @@ pub struct SourceFile { /// Get the path of the source file. #[get = "pub"] path: PathBuf, + /// Get the identifier of the source file. + #[get = "pub"] + identifier: String, /// Get the content of the source file #[get = "pub"] content: String, @@ -38,11 +41,12 @@ impl Debug for SourceFile { } impl SourceFile { - fn new(path: PathBuf, content: String) -> Arc { + fn new(path: PathBuf, identifier: String, content: String) -> Arc { let lines = get_line_byte_positions(&content); Arc::new(Self { path, + identifier, content, lines, }) @@ -83,9 +87,13 @@ impl SourceFile { /// /// # Errors /// - [`Error::IoError`]: Error occurred when reading the file contents. - pub fn load(path: &Path, provider: &impl FileProvider) -> Result, Error> { + pub fn load( + path: &Path, + identifier: String, + provider: &impl FileProvider, + ) -> Result, Error> { let source = provider.read_to_string(path)?; - Ok(Self::new(path.to_path_buf(), source)) + Ok(Self::new(path.to_path_buf(), identifier, source)) } /// Get the [`Location`] of a given byte index diff --git a/src/lexical/token.rs b/src/lexical/token.rs index ec43260..8cb964b 100644 --- a/src/lexical/token.rs +++ b/src/lexical/token.rs @@ -628,7 +628,8 @@ mod tests { let source_file = VFile::Text(String::from(content)); let mut vfolder = VFolder::new(); vfolder.add_file("test.shu", source_file); - let source_file = SourceFile::load(Path::new("test.shu"), &vfolder).unwrap(); + let source_file = + SourceFile::load(Path::new("test.shu"), "test".to_string(), &vfolder).unwrap(); Span::new(source_file, 0, content.len()).unwrap() } diff --git a/src/lib.rs b/src/lib.rs index 397d73b..90fd7b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,17 +39,18 @@ use crate::lexical::token_stream::TokenStream; /// use std::path::Path; /// use shulkerscript::{tokenize, base::{FsProvider, PrintHandler}}; /// -/// let token_stream = tokenize(&PrintHandler::new(), &FsProvider::default(), Path::new("path/to/file.shu"))?; +/// let token_stream = tokenize(&PrintHandler::new(), &FsProvider::default(), Path::new("path/to/file.shu"), "file".to_string())?; /// # Ok::<(), shulkerscript::base::Error>(()) /// ``` pub fn tokenize( handler: &impl Handler, file_provider: &impl FileProvider, path: &Path, + identifier: String, ) -> Result { tracing::info!("Tokenizing the source code at path: {}", path.display()); - let source_file = SourceFile::load(path, file_provider)?; + let source_file = SourceFile::load(path, identifier, file_provider)?; Ok(TokenStream::tokenize(&source_file, handler)) } @@ -65,15 +66,16 @@ pub fn tokenize( /// use std::path::Path; /// use shulkerscript::{parse, base::{FsProvider, PrintHandler}}; /// -/// let program_file = parse(&PrintHandler::new(), &FsProvider::default(), Path::new("path/to/file.shu"))?; +/// let program_file = parse(&PrintHandler::new(), &FsProvider::default(), Path::new("path/to/file.shu"), "file".to_string())?; /// # Ok::<(), shulkerscript::base::Error>(()) /// ``` pub fn parse( handler: &impl Handler, file_provider: &impl FileProvider, path: &Path, + identifier: String, ) -> Result { - let tokens = tokenize(handler, file_provider, path)?; + let tokens = tokenize(handler, file_provider, path, identifier)?; if handler.has_received() { return Err(Error::Other( @@ -137,9 +139,14 @@ where let programs = script_paths .iter() .map(|(program_identifier, path)| { - let program = parse(handler, file_provider, path.as_ref())?; + let program = parse( + handler, + file_provider, + path.as_ref(), + program_identifier.clone(), + )?; - Ok((program_identifier, program)) + Ok(program) }) .collect::>(); diff --git a/src/syntax/error.rs b/src/syntax/error.rs index ffcaf84..f811ef5 100644 --- a/src/syntax/error.rs +++ b/src/syntax/error.rs @@ -75,7 +75,7 @@ impl Display for UnexpectedSyntax { write!( f, "\n{}", - SourceCodeDisplay::new(span.span(), Option::::None) + SourceCodeDisplay::new(span.span(), Option::::None) ) }) } diff --git a/src/transpile/error.rs b/src/transpile/error.rs index c6a888a..cd26669 100644 --- a/src/transpile/error.rs +++ b/src/transpile/error.rs @@ -1,13 +1,21 @@ //! Errors that can occur during transpilation. -use crate::{base::source_file::SourceElement, syntax::syntax_tree::expression::Expression}; +use std::fmt::Display; + +use crate::{ + base::{ + log::{Message, Severity, SourceCodeDisplay}, + source_file::{SourceElement, Span}, + }, + syntax::syntax_tree::expression::Expression, +}; /// Errors that can occur during transpilation. #[allow(clippy::module_name_repetitions, missing_docs)] #[derive(Debug, thiserror::Error, Clone)] pub enum TranspileError { - #[error("Function {} was called but never declared.", .0)] - MissingFunctionDeclaration(String), + #[error(transparent)] + MissingFunctionDeclaration(#[from] MissingFunctionDeclaration), #[error("Unexpected expression: {}", .0.span().str())] UnexpectedExpression(Expression), #[error("Lua code evaluation is disabled.")] @@ -18,3 +26,27 @@ pub enum TranspileError { /// The result of a transpilation operation. pub type TranspileResult = Result; + +/// An error that occurs when a function declaration is missing. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct MissingFunctionDeclaration { + pub span: Span, +} + +impl Display for MissingFunctionDeclaration { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let message = format!( + "no matching function declaration found for invocation of function `{}`", + self.span.str() + ); + write!(f, "{}", Message::new(Severity::Error, message))?; + + write!( + f, + "\n{}", + SourceCodeDisplay::new(&self.span, Option::::None) + ) + } +} + +impl std::error::Error for MissingFunctionDeclaration {} diff --git a/src/transpile/transpiler.rs b/src/transpile/transpiler.rs index 7b4d084..87015ac 100644 --- a/src/transpile/transpiler.rs +++ b/src/transpile/transpiler.rs @@ -6,7 +6,11 @@ use std::{collections::HashMap, iter, sync::RwLock}; use shulkerbox::datapack::{self, Command, Datapack, Execute}; use crate::{ - base::{self, source_file::SourceElement, Handler}, + base::{ + self, + source_file::{SourceElement, Span}, + Handler, + }, syntax::syntax_tree::{ declaration::{Declaration, ImportItems}, expression::{Expression, FunctionCall, Primary}, @@ -16,6 +20,7 @@ use crate::{ Statement, }, }, + transpile::error::MissingFunctionDeclaration, }; use super::error::{TranspileError, TranspileResult}; @@ -33,6 +38,7 @@ pub struct Transpiler { #[derive(Debug, Clone)] struct FunctionData { namespace: String, + identifier_span: Span, statements: Vec, public: bool, annotations: HashMap>, @@ -61,18 +67,15 @@ impl Transpiler { /// # Errors /// - [`TranspileError::MissingFunctionDeclaration`] If a called function is missing #[tracing::instrument(level = "trace", skip_all)] - pub fn transpile( + pub fn transpile( &mut self, - programs: &[(Ident, ProgramFile)], + programs: &[ProgramFile], handler: &impl Handler, - ) -> Result<(), TranspileError> - where - Ident: AsRef, - { + ) -> Result<(), TranspileError> { tracing::trace!("Transpiling program declarations"); - for (identifier, program) in programs { - self.transpile_program_declarations(program, identifier.as_ref(), handler); + for program in programs { + self.transpile_program_declarations(program, handler); } let mut always_transpile_functions = Vec::new(); @@ -80,12 +83,12 @@ impl Transpiler { #[allow(clippy::significant_drop_in_scrutinee)] { let functions = self.functions.read().unwrap(); - for (function_identifier, data) in functions.iter() { + for (_, data) in functions.iter() { let always_transpile_function = data.annotations.contains_key("tick") || data.annotations.contains_key("load") || data.annotations.contains_key("deobfuscate"); if always_transpile_function { - always_transpile_functions.push(function_identifier.to_owned()); + always_transpile_functions.push(data.identifier_span.clone()); }; } } @@ -95,8 +98,8 @@ impl Transpiler { always_transpile_functions ); - for (program_identifier, name) in always_transpile_functions { - self.get_or_transpile_function(&name, &program_identifier, handler)?; + for identifier_span in always_transpile_functions { + self.get_or_transpile_function(&identifier_span, handler)?; } Ok(()) @@ -106,13 +109,12 @@ impl Transpiler { fn transpile_program_declarations( &mut self, program: &ProgramFile, - identifier: &str, handler: &impl Handler, ) { let namespace = program.namespace(); for declaration in program.declarations() { - self.transpile_declaration(declaration, namespace, identifier, handler); + self.transpile_declaration(declaration, namespace, handler); } } @@ -122,12 +124,13 @@ impl Transpiler { &mut self, declaration: &Declaration, namespace: &Namespace, - program_identifier: &str, _handler: &impl Handler, ) { + let program_identifier = declaration.span().source_file().identifier().clone(); match declaration { Declaration::Function(function) => { - let name = function.identifier().span().str().to_string(); + let identifier_span = &function.identifier().span; + let name = identifier_span.str().to_string(); let statements = function.block().statements().clone(); let annotations = function .annotations() @@ -142,9 +145,10 @@ impl Transpiler { }) .collect(); self.functions.write().unwrap().insert( - (program_identifier.to_string(), name), + (program_identifier, name), FunctionData { namespace: namespace.namespace_name().str_content().to_string(), + identifier_span: identifier_span.clone(), statements, public: function.is_public(), annotations, @@ -154,7 +158,7 @@ impl Transpiler { Declaration::Import(import) => { let path = import.module().str_content(); let import_identifier = - super::util::calculate_import_identifier(program_identifier, path); + super::util::calculate_import_identifier(&program_identifier, path); let mut aliases = self.aliases.write().unwrap(); @@ -167,7 +171,7 @@ impl Transpiler { for item in items { let name = item.span.str(); aliases.insert( - (program_identifier.to_string(), name.to_string()), + (program_identifier.clone(), name.to_string()), (import_identifier.clone(), name.to_string()), ); } @@ -183,11 +187,14 @@ impl Transpiler { #[tracing::instrument(level = "trace", skip(self, handler))] fn get_or_transpile_function( &mut self, - name: &str, - program_identifier: &str, + identifier_span: &Span, handler: &impl Handler, ) -> TranspileResult { - let program_query = (program_identifier.to_string(), name.to_string()); + let program_identifier = identifier_span.source_file().identifier(); + let program_query = ( + program_identifier.to_string(), + identifier_span.str().to_string(), + ); let alias_query = { let aliases = self.aliases.read().unwrap(); aliases.get(&program_query).cloned() @@ -216,7 +223,11 @@ impl Transpiler { .and_then(|q| functions.get(&q).filter(|f| f.public)) }) .ok_or_else(|| { - let error = TranspileError::MissingFunctionDeclaration(name.to_string()); + let error = TranspileError::MissingFunctionDeclaration( + MissingFunctionDeclaration { + span: identifier_span.clone(), + }, + ); handler.receive(error.clone()); error })?; @@ -233,7 +244,10 @@ impl Transpiler { .and_then(|q| functions.get(&q).filter(|f| f.public)) }) .ok_or_else(|| { - let error = TranspileError::MissingFunctionDeclaration(name.to_string()); + let error = + TranspileError::MissingFunctionDeclaration(MissingFunctionDeclaration { + span: identifier_span.clone(), + }); handler.receive(error.clone()); error })?; @@ -243,12 +257,13 @@ impl Transpiler { .get("deobfuscate") .map_or_else( || { - let hash_data = program_identifier.to_string() + "\0" + name; + let hash_data = + program_identifier.to_string() + "\0" + identifier_span.str(); Some("shu/".to_string() + &md5::hash(hash_data).to_hex_lowercase()[..16]) }, Clone::clone, ) - .unwrap_or_else(|| name.to_string()); + .unwrap_or_else(|| identifier_span.str().to_string()); let function = self .datapack @@ -269,7 +284,10 @@ impl Transpiler { } self.function_locations.write().unwrap().insert( - (program_identifier.to_string(), name.to_string()), + ( + program_identifier.to_string(), + identifier_span.str().to_string(), + ), (function_location, function_data.public), ); } @@ -279,7 +297,10 @@ 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(name.to_string()); + let error = + TranspileError::MissingFunctionDeclaration(MissingFunctionDeclaration { + span: identifier_span.clone(), + }); handler.receive(error.clone()); error }) @@ -322,9 +343,9 @@ impl Transpiler { Ok(Some(literal_command.clean_command().into())) } Statement::Run(run) => match run.expression() { - Expression::Primary(Primary::FunctionCall(func)) => self - .transpile_function_call(func, program_identifier, handler) - .map(Some), + Expression::Primary(Primary::FunctionCall(func)) => { + self.transpile_function_call(func, handler).map(Some) + } Expression::Primary(Primary::StringLiteral(string)) => { Ok(Some(Command::Raw(string.str_content().to_string()))) } @@ -366,9 +387,9 @@ impl Transpiler { } #[allow(clippy::match_wildcard_for_single_variants)] Statement::Semicolon(semi) => match semi.expression() { - Expression::Primary(Primary::FunctionCall(func)) => self - .transpile_function_call(func, program_identifier, handler) - .map(Some), + Expression::Primary(Primary::FunctionCall(func)) => { + self.transpile_function_call(func, handler).map(Some) + } unexpected => { let error = TranspileError::UnexpectedExpression(unexpected.clone()); handler.receive(error.clone()); @@ -381,13 +402,9 @@ impl Transpiler { fn transpile_function_call( &mut self, func: &FunctionCall, - program_identifier: &str, handler: &impl Handler, ) -> TranspileResult { - let identifier = func.identifier().span(); - let identifier_name = identifier.str(); - let location = - self.get_or_transpile_function(identifier_name, program_identifier, handler)?; + let location = self.get_or_transpile_function(&func.identifier().span, handler)?; Ok(Command::Raw(format!("function {location}"))) } diff --git a/tests/parsing/main.rs b/tests/parsing/main.rs index 3c02106..decc9de 100644 --- a/tests/parsing/main.rs +++ b/tests/parsing/main.rs @@ -12,8 +12,13 @@ fn parsing_test1() { let mut dir = VFolder::new(); dir.add_file("test1.shu", VFile::Text(source.to_string())); - let parsed = shulkerscript::parse(&PrintHandler::default(), &dir, Path::new("test1.shu")) - .expect("Failed to parse"); + let parsed = shulkerscript::parse( + &PrintHandler::default(), + &dir, + Path::new("test1.shu"), + "test1".to_string(), + ) + .expect("Failed to parse"); assert_eq!( parsed.namespace().namespace_name().str_content(), @@ -49,6 +54,11 @@ fn parsing_invalid() { let mut dir = VFolder::new(); dir.add_file("invalid.shu", VFile::Text(source.to_string())); - shulkerscript::parse(&PrintHandler::default(), &dir, Path::new("invalid.shu")) - .expect_err("Expecting parsing failure"); + shulkerscript::parse( + &PrintHandler::default(), + &dir, + Path::new("invalid.shu"), + "invalid".to_string(), + ) + .expect_err("Expecting parsing failure"); }