add SourceCodeDisplay to MissingFunctionDeclaration error
This commit is contained in:
parent
4505def6c0
commit
659683bd39
|
@ -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),
|
||||
|
|
|
@ -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<Self> {
|
||||
fn new(path: PathBuf, identifier: String, content: String) -> Arc<Self> {
|
||||
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<Arc<Self>, Error> {
|
||||
pub fn load(
|
||||
path: &Path,
|
||||
identifier: String,
|
||||
provider: &impl FileProvider,
|
||||
) -> Result<Arc<Self>, 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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
19
src/lib.rs
19
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<base::Error>,
|
||||
file_provider: &impl FileProvider,
|
||||
path: &Path,
|
||||
identifier: String,
|
||||
) -> Result<TokenStream> {
|
||||
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<base::Error>,
|
||||
file_provider: &impl FileProvider,
|
||||
path: &Path,
|
||||
identifier: String,
|
||||
) -> Result<ProgramFile> {
|
||||
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::<Vec<_>>();
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ impl Display for UnexpectedSyntax {
|
|||
write!(
|
||||
f,
|
||||
"\n{}",
|
||||
SourceCodeDisplay::new(span.span(), Option::<i32>::None)
|
||||
SourceCodeDisplay::new(span.span(), Option::<u8>::None)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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<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 {}
|
||||
|
|
|
@ -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<Statement>,
|
||||
public: bool,
|
||||
annotations: HashMap<String, Option<String>>,
|
||||
|
@ -61,18 +67,15 @@ impl Transpiler {
|
|||
/// # Errors
|
||||
/// - [`TranspileError::MissingFunctionDeclaration`] If a called function is missing
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
pub fn transpile<Ident>(
|
||||
pub fn transpile(
|
||||
&mut self,
|
||||
programs: &[(Ident, ProgramFile)],
|
||||
programs: &[ProgramFile],
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<(), TranspileError>
|
||||
where
|
||||
Ident: AsRef<str>,
|
||||
{
|
||||
) -> 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<base::Error>,
|
||||
) {
|
||||
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<base::Error>,
|
||||
) {
|
||||
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<base::Error>,
|
||||
) -> 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 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<base::Error>,
|
||||
) -> TranspileResult<Command> {
|
||||
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}")))
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue