add SourceCodeDisplay to MissingFunctionDeclaration error

This commit is contained in:
Moritz Hölting 2024-08-27 22:55:26 +02:00
parent 4505def6c0
commit 659683bd39
8 changed files with 134 additions and 59 deletions

View File

@ -10,7 +10,7 @@ pub enum Error {
TokenizeError(#[from] crate::lexical::token::TokenizeError), TokenizeError(#[from] crate::lexical::token::TokenizeError),
#[error(transparent)] #[error(transparent)]
ParseError(#[from] crate::syntax::error::Error), ParseError(#[from] crate::syntax::error::Error),
#[error("An error occurred while transpiling the source code.")] #[error(transparent)]
TranspileError(#[from] crate::transpile::TranspileError), TranspileError(#[from] crate::transpile::TranspileError),
#[error("An error occurred")] #[error("An error occurred")]
Other(&'static str), Other(&'static str),

View File

@ -21,6 +21,9 @@ pub struct SourceFile {
/// Get the path of the source file. /// Get the path of the source file.
#[get = "pub"] #[get = "pub"]
path: PathBuf, path: PathBuf,
/// Get the identifier of the source file.
#[get = "pub"]
identifier: String,
/// Get the content of the source file /// Get the content of the source file
#[get = "pub"] #[get = "pub"]
content: String, content: String,
@ -38,11 +41,12 @@ impl Debug for SourceFile {
} }
impl SourceFile { impl SourceFile {
fn new(path: PathBuf, content: String) -> Arc<Self> { fn new(path: PathBuf, identifier: String, content: String) -> Arc<Self> {
let lines = get_line_byte_positions(&content); let lines = get_line_byte_positions(&content);
Arc::new(Self { Arc::new(Self {
path, path,
identifier,
content, content,
lines, lines,
}) })
@ -83,9 +87,13 @@ impl SourceFile {
/// ///
/// # Errors /// # Errors
/// - [`Error::IoError`]: Error occurred when reading the file contents. /// - [`Error::IoError`]: Error occurred when reading the file contents.
pub fn load(path: &Path, provider: &impl FileProvider) -> Result<Arc<Self>, Error> { pub fn load(
path: &Path,
identifier: String,
provider: &impl FileProvider,
) -> Result<Arc<Self>, Error> {
let source = provider.read_to_string(path)?; 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 /// Get the [`Location`] of a given byte index

View File

@ -628,7 +628,8 @@ mod tests {
let source_file = VFile::Text(String::from(content)); let source_file = VFile::Text(String::from(content));
let mut vfolder = VFolder::new(); let mut vfolder = VFolder::new();
vfolder.add_file("test.shu", source_file); 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() Span::new(source_file, 0, content.len()).unwrap()
} }

View File

@ -39,17 +39,18 @@ use crate::lexical::token_stream::TokenStream;
/// use std::path::Path; /// use std::path::Path;
/// use shulkerscript::{tokenize, base::{FsProvider, PrintHandler}}; /// 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>(()) /// # Ok::<(), shulkerscript::base::Error>(())
/// ``` /// ```
pub fn tokenize( pub fn tokenize(
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
file_provider: &impl FileProvider, file_provider: &impl FileProvider,
path: &Path, path: &Path,
identifier: String,
) -> Result<TokenStream> { ) -> Result<TokenStream> {
tracing::info!("Tokenizing the source code at path: {}", path.display()); 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)) Ok(TokenStream::tokenize(&source_file, handler))
} }
@ -65,15 +66,16 @@ pub fn tokenize(
/// use std::path::Path; /// use std::path::Path;
/// use shulkerscript::{parse, base::{FsProvider, PrintHandler}}; /// 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>(()) /// # Ok::<(), shulkerscript::base::Error>(())
/// ``` /// ```
pub fn parse( pub fn parse(
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
file_provider: &impl FileProvider, file_provider: &impl FileProvider,
path: &Path, path: &Path,
identifier: String,
) -> Result<ProgramFile> { ) -> Result<ProgramFile> {
let tokens = tokenize(handler, file_provider, path)?; let tokens = tokenize(handler, file_provider, path, identifier)?;
if handler.has_received() { if handler.has_received() {
return Err(Error::Other( return Err(Error::Other(
@ -137,9 +139,14 @@ where
let programs = script_paths let programs = script_paths
.iter() .iter()
.map(|(program_identifier, path)| { .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::<Vec<_>>(); .collect::<Vec<_>>();

View File

@ -75,7 +75,7 @@ impl Display for UnexpectedSyntax {
write!( write!(
f, f,
"\n{}", "\n{}",
SourceCodeDisplay::new(span.span(), Option::<i32>::None) SourceCodeDisplay::new(span.span(), Option::<u8>::None)
) )
}) })
} }

View File

@ -1,13 +1,21 @@
//! Errors that can occur during transpilation. //! 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. /// 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)]
pub enum TranspileError { pub enum TranspileError {
#[error("Function {} was called but never declared.", .0)] #[error(transparent)]
MissingFunctionDeclaration(String), MissingFunctionDeclaration(#[from] MissingFunctionDeclaration),
#[error("Unexpected expression: {}", .0.span().str())] #[error("Unexpected expression: {}", .0.span().str())]
UnexpectedExpression(Expression), UnexpectedExpression(Expression),
#[error("Lua code evaluation is disabled.")] #[error("Lua code evaluation is disabled.")]
@ -18,3 +26,27 @@ pub enum TranspileError {
/// The result of a transpilation operation. /// The result of a transpilation operation.
pub type TranspileResult<T> = Result<T, TranspileError>; pub type TranspileResult<T> = Result<T, TranspileError>;
/// 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::<u8>::None)
)
}
}
impl std::error::Error for MissingFunctionDeclaration {}

View File

@ -6,7 +6,11 @@ use std::{collections::HashMap, iter, sync::RwLock};
use shulkerbox::datapack::{self, Command, Datapack, Execute}; use shulkerbox::datapack::{self, Command, Datapack, Execute};
use crate::{ use crate::{
base::{self, source_file::SourceElement, Handler}, base::{
self,
source_file::{SourceElement, Span},
Handler,
},
syntax::syntax_tree::{ syntax::syntax_tree::{
declaration::{Declaration, ImportItems}, declaration::{Declaration, ImportItems},
expression::{Expression, FunctionCall, Primary}, expression::{Expression, FunctionCall, Primary},
@ -16,6 +20,7 @@ use crate::{
Statement, Statement,
}, },
}, },
transpile::error::MissingFunctionDeclaration,
}; };
use super::error::{TranspileError, TranspileResult}; use super::error::{TranspileError, TranspileResult};
@ -33,6 +38,7 @@ pub struct Transpiler {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct FunctionData { struct FunctionData {
namespace: String, namespace: String,
identifier_span: Span,
statements: Vec<Statement>, statements: Vec<Statement>,
public: bool, public: bool,
annotations: HashMap<String, Option<String>>, annotations: HashMap<String, Option<String>>,
@ -61,18 +67,15 @@ impl Transpiler {
/// # Errors /// # Errors
/// - [`TranspileError::MissingFunctionDeclaration`] If a called function is missing /// - [`TranspileError::MissingFunctionDeclaration`] If a called function is missing
#[tracing::instrument(level = "trace", skip_all)] #[tracing::instrument(level = "trace", skip_all)]
pub fn transpile<Ident>( pub fn transpile(
&mut self, &mut self,
programs: &[(Ident, ProgramFile)], programs: &[ProgramFile],
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> Result<(), TranspileError> ) -> Result<(), TranspileError> {
where
Ident: AsRef<str>,
{
tracing::trace!("Transpiling program declarations"); tracing::trace!("Transpiling program declarations");
for (identifier, program) in programs { for program in programs {
self.transpile_program_declarations(program, identifier.as_ref(), handler); self.transpile_program_declarations(program, handler);
} }
let mut always_transpile_functions = Vec::new(); let mut always_transpile_functions = Vec::new();
@ -80,12 +83,12 @@ impl Transpiler {
#[allow(clippy::significant_drop_in_scrutinee)] #[allow(clippy::significant_drop_in_scrutinee)]
{ {
let functions = self.functions.read().unwrap(); 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") let always_transpile_function = data.annotations.contains_key("tick")
|| data.annotations.contains_key("load") || data.annotations.contains_key("load")
|| data.annotations.contains_key("deobfuscate"); || data.annotations.contains_key("deobfuscate");
if always_transpile_function { 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 always_transpile_functions
); );
for (program_identifier, name) in always_transpile_functions { for identifier_span in always_transpile_functions {
self.get_or_transpile_function(&name, &program_identifier, handler)?; self.get_or_transpile_function(&identifier_span, handler)?;
} }
Ok(()) Ok(())
@ -106,13 +109,12 @@ impl Transpiler {
fn transpile_program_declarations( fn transpile_program_declarations(
&mut self, &mut self,
program: &ProgramFile, program: &ProgramFile,
identifier: &str,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) { ) {
let namespace = program.namespace(); let namespace = program.namespace();
for declaration in program.declarations() { 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, &mut self,
declaration: &Declaration, declaration: &Declaration,
namespace: &Namespace, namespace: &Namespace,
program_identifier: &str,
_handler: &impl Handler<base::Error>, _handler: &impl Handler<base::Error>,
) { ) {
let program_identifier = declaration.span().source_file().identifier().clone();
match declaration { match declaration {
Declaration::Function(function) => { 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 statements = function.block().statements().clone();
let annotations = function let annotations = function
.annotations() .annotations()
@ -142,9 +145,10 @@ impl Transpiler {
}) })
.collect(); .collect();
self.functions.write().unwrap().insert( self.functions.write().unwrap().insert(
(program_identifier.to_string(), name), (program_identifier, name),
FunctionData { FunctionData {
namespace: namespace.namespace_name().str_content().to_string(), namespace: namespace.namespace_name().str_content().to_string(),
identifier_span: identifier_span.clone(),
statements, statements,
public: function.is_public(), public: function.is_public(),
annotations, annotations,
@ -154,7 +158,7 @@ impl Transpiler {
Declaration::Import(import) => { Declaration::Import(import) => {
let path = import.module().str_content(); let path = import.module().str_content();
let import_identifier = 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(); let mut aliases = self.aliases.write().unwrap();
@ -167,7 +171,7 @@ impl Transpiler {
for item in items { for item in items {
let name = item.span.str(); let name = item.span.str();
aliases.insert( aliases.insert(
(program_identifier.to_string(), name.to_string()), (program_identifier.clone(), name.to_string()),
(import_identifier.clone(), name.to_string()), (import_identifier.clone(), name.to_string()),
); );
} }
@ -183,11 +187,14 @@ impl Transpiler {
#[tracing::instrument(level = "trace", skip(self, handler))] #[tracing::instrument(level = "trace", skip(self, handler))]
fn get_or_transpile_function( fn get_or_transpile_function(
&mut self, &mut self,
name: &str, identifier_span: &Span,
program_identifier: &str,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> TranspileResult<String> { ) -> TranspileResult<String> {
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 alias_query = {
let aliases = self.aliases.read().unwrap(); let aliases = self.aliases.read().unwrap();
aliases.get(&program_query).cloned() aliases.get(&program_query).cloned()
@ -216,7 +223,11 @@ 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 = TranspileError::MissingFunctionDeclaration(name.to_string()); let error = TranspileError::MissingFunctionDeclaration(
MissingFunctionDeclaration {
span: identifier_span.clone(),
},
);
handler.receive(error.clone()); handler.receive(error.clone());
error error
})?; })?;
@ -233,7 +244,10 @@ 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 = TranspileError::MissingFunctionDeclaration(name.to_string()); let error =
TranspileError::MissingFunctionDeclaration(MissingFunctionDeclaration {
span: identifier_span.clone(),
});
handler.receive(error.clone()); handler.receive(error.clone());
error error
})?; })?;
@ -243,12 +257,13 @@ impl Transpiler {
.get("deobfuscate") .get("deobfuscate")
.map_or_else( .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]) Some("shu/".to_string() + &md5::hash(hash_data).to_hex_lowercase()[..16])
}, },
Clone::clone, Clone::clone,
) )
.unwrap_or_else(|| name.to_string()); .unwrap_or_else(|| identifier_span.str().to_string());
let function = self let function = self
.datapack .datapack
@ -269,7 +284,10 @@ impl Transpiler {
} }
self.function_locations.write().unwrap().insert( 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), (function_location, function_data.public),
); );
} }
@ -279,7 +297,10 @@ 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 = TranspileError::MissingFunctionDeclaration(name.to_string()); let error =
TranspileError::MissingFunctionDeclaration(MissingFunctionDeclaration {
span: identifier_span.clone(),
});
handler.receive(error.clone()); handler.receive(error.clone());
error error
}) })
@ -322,9 +343,9 @@ impl Transpiler {
Ok(Some(literal_command.clean_command().into())) Ok(Some(literal_command.clean_command().into()))
} }
Statement::Run(run) => match run.expression() { Statement::Run(run) => match run.expression() {
Expression::Primary(Primary::FunctionCall(func)) => self Expression::Primary(Primary::FunctionCall(func)) => {
.transpile_function_call(func, program_identifier, handler) self.transpile_function_call(func, handler).map(Some)
.map(Some), }
Expression::Primary(Primary::StringLiteral(string)) => { Expression::Primary(Primary::StringLiteral(string)) => {
Ok(Some(Command::Raw(string.str_content().to_string()))) Ok(Some(Command::Raw(string.str_content().to_string())))
} }
@ -366,9 +387,9 @@ impl Transpiler {
} }
#[allow(clippy::match_wildcard_for_single_variants)] #[allow(clippy::match_wildcard_for_single_variants)]
Statement::Semicolon(semi) => match semi.expression() { Statement::Semicolon(semi) => match semi.expression() {
Expression::Primary(Primary::FunctionCall(func)) => self Expression::Primary(Primary::FunctionCall(func)) => {
.transpile_function_call(func, program_identifier, handler) self.transpile_function_call(func, handler).map(Some)
.map(Some), }
unexpected => { unexpected => {
let error = TranspileError::UnexpectedExpression(unexpected.clone()); let error = TranspileError::UnexpectedExpression(unexpected.clone());
handler.receive(error.clone()); handler.receive(error.clone());
@ -381,13 +402,9 @@ impl Transpiler {
fn transpile_function_call( fn transpile_function_call(
&mut self, &mut self,
func: &FunctionCall, func: &FunctionCall,
program_identifier: &str,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> TranspileResult<Command> { ) -> TranspileResult<Command> {
let identifier = func.identifier().span(); let location = self.get_or_transpile_function(&func.identifier().span, handler)?;
let identifier_name = identifier.str();
let location =
self.get_or_transpile_function(identifier_name, program_identifier, handler)?;
Ok(Command::Raw(format!("function {location}"))) Ok(Command::Raw(format!("function {location}")))
} }

View File

@ -12,8 +12,13 @@ fn parsing_test1() {
let mut dir = VFolder::new(); let mut dir = VFolder::new();
dir.add_file("test1.shu", VFile::Text(source.to_string())); dir.add_file("test1.shu", VFile::Text(source.to_string()));
let parsed = shulkerscript::parse(&PrintHandler::default(), &dir, Path::new("test1.shu")) let parsed = shulkerscript::parse(
.expect("Failed to parse"); &PrintHandler::default(),
&dir,
Path::new("test1.shu"),
"test1".to_string(),
)
.expect("Failed to parse");
assert_eq!( assert_eq!(
parsed.namespace().namespace_name().str_content(), parsed.namespace().namespace_name().str_content(),
@ -49,6 +54,11 @@ fn parsing_invalid() {
let mut dir = VFolder::new(); let mut dir = VFolder::new();
dir.add_file("invalid.shu", VFile::Text(source.to_string())); dir.add_file("invalid.shu", VFile::Text(source.to_string()));
shulkerscript::parse(&PrintHandler::default(), &dir, Path::new("invalid.shu")) shulkerscript::parse(
.expect_err("Expecting parsing failure"); &PrintHandler::default(),
&dir,
Path::new("invalid.shu"),
"invalid".to_string(),
)
.expect_err("Expecting parsing failure");
} }