require macros to be present in function parameters
This commit is contained in:
parent
b2cc3bb4c7
commit
eb595bc28b
|
@ -37,7 +37,7 @@ path-absolutize = "3.1.1"
|
|||
pathdiff = "0.2.2"
|
||||
serde = { version = "1.0.214", features = ["derive", "rc"], optional = true }
|
||||
# shulkerbox = { version = "0.1.0", default-features = false, optional = true }
|
||||
shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", rev = "8f05fef7030d3e999a07d621ba581ebbb205dadc", default-features = false, optional = true }
|
||||
shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", rev = "76d58c0766518fe5ab2635de60ba40972565a3e0", default-features = false, optional = true }
|
||||
strsim = "0.11.1"
|
||||
strum = { version = "0.26.2", features = ["derive"] }
|
||||
strum_macros = "0.26.4"
|
||||
|
|
|
@ -9,6 +9,8 @@ pub enum Error {
|
|||
#[error(transparent)]
|
||||
ParseError(#[from] crate::syntax::error::Error),
|
||||
#[error(transparent)]
|
||||
SemanticError(#[from] crate::semantic::error::Error),
|
||||
#[error(transparent)]
|
||||
TranspileError(#[from] crate::transpile::TranspileError),
|
||||
#[error("An error occurred: {0}")]
|
||||
Other(String),
|
||||
|
|
|
@ -16,6 +16,7 @@ pub use shulkerbox;
|
|||
|
||||
pub mod base;
|
||||
pub mod lexical;
|
||||
pub mod semantic;
|
||||
pub mod syntax;
|
||||
pub mod transpile;
|
||||
|
||||
|
@ -99,6 +100,8 @@ pub fn parse(
|
|||
));
|
||||
}
|
||||
|
||||
program.analyze_semantics(handler)?;
|
||||
|
||||
Ok(program)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,214 @@
|
|||
//! Error types for the semantic analysis phase of the compiler.
|
||||
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use std::{collections::HashSet, fmt::Display};
|
||||
|
||||
use getset::Getters;
|
||||
use itertools::Itertools as _;
|
||||
|
||||
use crate::{
|
||||
base::{
|
||||
log::{Message, Severity, SourceCodeDisplay},
|
||||
source_file::{SourceElement as _, Span},
|
||||
},
|
||||
lexical::token::StringLiteral,
|
||||
syntax::syntax_tree::expression::Expression,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
MissingFunctionDeclaration(#[from] MissingFunctionDeclaration),
|
||||
#[error(transparent)]
|
||||
UnexpectedExpression(#[from] UnexpectedExpression),
|
||||
#[error(transparent)]
|
||||
ConflictingFunctionNames(#[from] ConflictingFunctionNames),
|
||||
#[error(transparent)]
|
||||
InvalidNamespaceName(#[from] InvalidNamespaceName),
|
||||
#[error(transparent)]
|
||||
UnresolvedMacroUsage(#[from] UnresolvedMacroUsage),
|
||||
}
|
||||
|
||||
/// An error that occurs when a function declaration is missing.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Getters)]
|
||||
pub struct MissingFunctionDeclaration {
|
||||
#[get = "pub"]
|
||||
span: Span,
|
||||
#[get = "pub"]
|
||||
alternatives: Vec<String>,
|
||||
}
|
||||
|
||||
impl MissingFunctionDeclaration {
|
||||
pub(super) fn from_context(identifier_span: Span, functions: &HashSet<String>) -> Self {
|
||||
let own_name = identifier_span.str();
|
||||
let alternatives = functions
|
||||
.iter()
|
||||
.filter_map(|function_name| {
|
||||
let normalized_distance =
|
||||
strsim::normalized_damerau_levenshtein(own_name, function_name);
|
||||
(normalized_distance > 0.8
|
||||
|| strsim::damerau_levenshtein(own_name, function_name) < 3)
|
||||
.then_some((normalized_distance, function_name))
|
||||
})
|
||||
.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 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use std::fmt::Write;
|
||||
|
||||
let message = format!(
|
||||
"no matching function declaration found for invocation of function `{}`",
|
||||
self.span.str()
|
||||
);
|
||||
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(", ");
|
||||
}
|
||||
write!(message, "`{alternative}`")?;
|
||||
}
|
||||
Some(message + "?")
|
||||
};
|
||||
|
||||
write!(
|
||||
f,
|
||||
"\n{}",
|
||||
SourceCodeDisplay::new(&self.span, help_message.as_ref())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for MissingFunctionDeclaration {}
|
||||
|
||||
/// An error that occurs when a function declaration is missing.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct UnexpectedExpression(pub Expression);
|
||||
|
||||
impl Display for UnexpectedExpression {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
Message::new(Severity::Error, "encountered unexpected expression")
|
||||
)?;
|
||||
|
||||
write!(
|
||||
f,
|
||||
"\n{}",
|
||||
SourceCodeDisplay::new(&self.0.span(), Option::<u8>::None)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for UnexpectedExpression {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ConflictingFunctionNames {
|
||||
pub definition: Span,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl Display for ConflictingFunctionNames {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
Message::new(
|
||||
Severity::Error,
|
||||
format!("the following function declaration conflicts with an existing function with name `{}`", self.name)
|
||||
)
|
||||
)?;
|
||||
|
||||
write!(
|
||||
f,
|
||||
"\n{}",
|
||||
SourceCodeDisplay::new(&self.definition, Option::<u8>::None)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ConflictingFunctionNames {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct InvalidNamespaceName {
|
||||
pub name: StringLiteral,
|
||||
pub invalid_chars: String,
|
||||
}
|
||||
|
||||
impl Display for InvalidNamespaceName {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
Message::new(
|
||||
Severity::Error,
|
||||
format!(
|
||||
"Invalid characters in namespace `{}`. The following characters are not allowed in namespace definitions: `{}`",
|
||||
self.name.str_content(),
|
||||
self.invalid_chars
|
||||
)
|
||||
)
|
||||
)?;
|
||||
|
||||
write!(
|
||||
f,
|
||||
"\n{}",
|
||||
SourceCodeDisplay::new(&self.name.span, Option::<u8>::None)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for InvalidNamespaceName {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct UnresolvedMacroUsage {
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
impl Display for UnresolvedMacroUsage {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
Message::new(
|
||||
Severity::Error,
|
||||
format!(
|
||||
"Macro `{}` was used, but could not be resolved.",
|
||||
self.span.str(),
|
||||
)
|
||||
)
|
||||
)?;
|
||||
|
||||
write!(
|
||||
f,
|
||||
"\n{}",
|
||||
SourceCodeDisplay::new(
|
||||
&self.span,
|
||||
Some(format!(
|
||||
"You might want to add `{}` to the function parameters.",
|
||||
self.span.str()
|
||||
))
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for UnresolvedMacroUsage {}
|
|
@ -0,0 +1,526 @@
|
|||
//! This module contains the semantic analysis of the AST.
|
||||
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use error::{
|
||||
InvalidNamespaceName, MissingFunctionDeclaration, UnexpectedExpression, UnresolvedMacroUsage,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
base::{self, source_file::SourceElement as _, Handler},
|
||||
lexical::token::{MacroStringLiteral, MacroStringLiteralPart},
|
||||
syntax::syntax_tree::{
|
||||
condition::{
|
||||
BinaryCondition, Condition, ParenthesizedCondition, PrimaryCondition, UnaryCondition,
|
||||
},
|
||||
declaration::{Declaration, Function, ImportItems},
|
||||
expression::{Expression, FunctionCall, Primary},
|
||||
program::{Namespace, ProgramFile},
|
||||
statement::{
|
||||
execute_block::{
|
||||
Conditional, Else, ExecuteBlock, ExecuteBlockHead, ExecuteBlockHeadItem as _,
|
||||
ExecuteBlockTail,
|
||||
},
|
||||
Block, Grouping, Run, Semicolon, Statement,
|
||||
},
|
||||
AnyStringLiteral,
|
||||
},
|
||||
};
|
||||
|
||||
pub mod error;
|
||||
|
||||
impl ProgramFile {
|
||||
/// Analyzes the semantics of the program.
|
||||
pub fn analyze_semantics(
|
||||
&self,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<(), error::Error> {
|
||||
self.namespace().analyze_semantics(handler)?;
|
||||
|
||||
let function_names = extract_all_function_names(self.declarations(), handler)?;
|
||||
|
||||
for declaration in self.declarations() {
|
||||
declaration.analyze_semantics(&function_names, handler)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_all_function_names(
|
||||
declarations: &[Declaration],
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<HashSet<String>, error::Error> {
|
||||
let mut function_names = HashSet::new();
|
||||
let mut errs = Vec::new();
|
||||
|
||||
for declaration in declarations {
|
||||
match declaration {
|
||||
Declaration::Function(func) => {
|
||||
let name = func.identifier();
|
||||
if function_names.contains(name.span.str()) {
|
||||
let err = error::Error::from(error::ConflictingFunctionNames {
|
||||
name: name.span.str().to_string(),
|
||||
definition: name.span(),
|
||||
});
|
||||
handler.receive(err.clone());
|
||||
errs.push(err);
|
||||
}
|
||||
function_names.insert(name.span.str().to_string());
|
||||
}
|
||||
|
||||
Declaration::Import(imp) => match imp.items() {
|
||||
ImportItems::All(_) => {
|
||||
handler.receive(base::Error::Other(
|
||||
"Importing all items is not yet supported.".to_string(),
|
||||
));
|
||||
}
|
||||
ImportItems::Named(items) => {
|
||||
for item in items.elements() {
|
||||
if function_names.contains(item.span.str()) {
|
||||
let err = error::Error::from(error::ConflictingFunctionNames {
|
||||
name: item.span.str().to_string(),
|
||||
definition: item.span(),
|
||||
});
|
||||
handler.receive(err.clone());
|
||||
errs.push(err);
|
||||
}
|
||||
function_names.insert(item.span.str().to_string());
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Declaration::Tag(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::option_if_let_else)]
|
||||
if let Some(err) = errs.first() {
|
||||
Err(err.clone())
|
||||
} else {
|
||||
Ok(function_names)
|
||||
}
|
||||
}
|
||||
|
||||
impl Namespace {
|
||||
/// Analyzes the semantics of the namespace.
|
||||
pub fn analyze_semantics(
|
||||
&self,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<(), error::Error> {
|
||||
let name = self.namespace_name();
|
||||
Self::validate_str(name.str_content().as_ref()).map_err(|invalid_chars| {
|
||||
let err = error::Error::from(InvalidNamespaceName {
|
||||
name: name.clone(),
|
||||
invalid_chars,
|
||||
});
|
||||
handler.receive(err.clone());
|
||||
err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Declaration {
|
||||
/// Analyzes the semantics of the declaration.
|
||||
pub fn analyze_semantics(
|
||||
&self,
|
||||
function_names: &HashSet<String>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<(), error::Error> {
|
||||
match self {
|
||||
Self::Function(func) => func.analyze_semantics(function_names, handler),
|
||||
Self::Import(_) | Self::Tag(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Function {
|
||||
/// Analyzes the semantics of the function.
|
||||
pub fn analyze_semantics(
|
||||
&self,
|
||||
function_names: &HashSet<String>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<(), error::Error> {
|
||||
let macro_names = self
|
||||
.parameters()
|
||||
.iter()
|
||||
.flat_map(|l| l.elements().map(|el| el.span.str().to_string()))
|
||||
.collect();
|
||||
|
||||
self.block()
|
||||
.analyze_semantics(function_names, ¯o_names, handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl Block {
|
||||
/// Analyzes the semantics of a block.
|
||||
pub fn analyze_semantics(
|
||||
&self,
|
||||
function_names: &HashSet<String>,
|
||||
macro_names: &HashSet<String>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<(), error::Error> {
|
||||
for statement in &self.statements {
|
||||
match statement {
|
||||
Statement::Block(block) => {
|
||||
block.analyze_semantics(function_names, macro_names, handler)?;
|
||||
}
|
||||
Statement::DocComment(_) | Statement::LiteralCommand(_) => {}
|
||||
Statement::ExecuteBlock(ex) => {
|
||||
ex.analyze_semantics(function_names, macro_names, handler)?;
|
||||
}
|
||||
Statement::Grouping(group) => {
|
||||
group.analyze_semantics(function_names, macro_names, handler)?;
|
||||
}
|
||||
Statement::Run(run) => {
|
||||
run.analyze_semantics(function_names, macro_names, handler)?;
|
||||
}
|
||||
Statement::Semicolon(sem) => {
|
||||
sem.analyze_semantics(function_names, macro_names, handler)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteBlock {
|
||||
/// Analyzes the semantics of the execute block.
|
||||
pub fn analyze_semantics(
|
||||
&self,
|
||||
function_names: &HashSet<String>,
|
||||
macro_names: &HashSet<String>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<(), error::Error> {
|
||||
match self {
|
||||
Self::HeadTail(head, tail) => {
|
||||
head.analyze_semantics(function_names, macro_names, handler)?;
|
||||
tail.analyze_semantics(function_names, macro_names, handler)
|
||||
}
|
||||
Self::IfElse(cond, then, el) => {
|
||||
cond.analyze_semantics(function_names, macro_names, handler)?;
|
||||
then.analyze_semantics(function_names, macro_names, handler)?;
|
||||
el.analyze_semantics(function_names, macro_names, handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Grouping {
|
||||
/// Analyzes the semantics of the grouping.
|
||||
pub fn analyze_semantics(
|
||||
&self,
|
||||
function_names: &HashSet<String>,
|
||||
macro_names: &HashSet<String>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<(), error::Error> {
|
||||
self.block()
|
||||
.analyze_semantics(function_names, macro_names, handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl Run {
|
||||
/// Analyzes the semantics of the run statement.
|
||||
pub fn analyze_semantics(
|
||||
&self,
|
||||
function_names: &HashSet<String>,
|
||||
macro_names: &HashSet<String>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<(), error::Error> {
|
||||
self.expression()
|
||||
.analyze_semantics(function_names, macro_names, handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl Semicolon {
|
||||
/// Analyzes the semantics of the semicolon statement.
|
||||
pub fn analyze_semantics(
|
||||
&self,
|
||||
function_names: &HashSet<String>,
|
||||
macro_names: &HashSet<String>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<(), error::Error> {
|
||||
match self.expression() {
|
||||
Expression::Primary(Primary::FunctionCall(func)) => {
|
||||
func.analyze_semantics(function_names, macro_names, handler)
|
||||
}
|
||||
Expression::Primary(unexpected) => {
|
||||
let error = error::Error::UnexpectedExpression(UnexpectedExpression(
|
||||
Expression::Primary(unexpected.clone()),
|
||||
));
|
||||
handler.receive(error.clone());
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteBlockHead {
|
||||
/// Analyzes the semantics of the execute block head.
|
||||
pub fn analyze_semantics(
|
||||
&self,
|
||||
function_names: &HashSet<String>,
|
||||
macro_names: &HashSet<String>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<(), error::Error> {
|
||||
match self {
|
||||
Self::Align(align) => align.analyze_semantics(macro_names, handler),
|
||||
Self::Anchored(anchored) => anchored.analyze_semantics(macro_names, handler),
|
||||
Self::As(r#as) => r#as.analyze_semantics(macro_names, handler),
|
||||
Self::At(at) => at.analyze_semantics(macro_names, handler),
|
||||
Self::AsAt(asat) => asat.analyze_semantics(macro_names, handler),
|
||||
Self::Conditional(cond) => cond.analyze_semantics(function_names, macro_names, handler),
|
||||
Self::Facing(facing) => facing.analyze_semantics(macro_names, handler),
|
||||
Self::In(r#in) => r#in.analyze_semantics(macro_names, handler),
|
||||
Self::On(on) => on.analyze_semantics(macro_names, handler),
|
||||
Self::Positioned(pos) => pos.analyze_semantics(macro_names, handler),
|
||||
Self::Rotated(rot) => rot.analyze_semantics(macro_names, handler),
|
||||
Self::Store(store) => store.analyze_semantics(macro_names, handler),
|
||||
Self::Summon(summon) => summon.analyze_semantics(macro_names, handler),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteBlockTail {
|
||||
/// Analyzes the semantics of the execute block tail.
|
||||
pub fn analyze_semantics(
|
||||
&self,
|
||||
function_names: &HashSet<String>,
|
||||
macro_names: &HashSet<String>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<(), error::Error> {
|
||||
match self {
|
||||
Self::Block(block) => block.analyze_semantics(function_names, macro_names, handler),
|
||||
Self::ExecuteBlock(_, ex) => ex.analyze_semantics(function_names, macro_names, handler),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Conditional {
|
||||
/// Analyzes the semantics of the conditional.
|
||||
pub fn analyze_semantics(
|
||||
&self,
|
||||
function_names: &HashSet<String>,
|
||||
macro_names: &HashSet<String>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<(), error::Error> {
|
||||
self.condition()
|
||||
.analyze_semantics(function_names, macro_names, handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParenthesizedCondition {
|
||||
/// Analyzes the semantics of the parenthesized condition.
|
||||
pub fn analyze_semantics(
|
||||
&self,
|
||||
function_names: &HashSet<String>,
|
||||
macro_names: &HashSet<String>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<(), error::Error> {
|
||||
self.condition
|
||||
.analyze_semantics(function_names, macro_names, handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl Condition {
|
||||
/// Analyzes the semantics of the condition.
|
||||
pub fn analyze_semantics(
|
||||
&self,
|
||||
function_names: &HashSet<String>,
|
||||
macro_names: &HashSet<String>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<(), error::Error> {
|
||||
match self {
|
||||
Self::Primary(prim) => prim.analyze_semantics(function_names, macro_names, handler),
|
||||
Self::Binary(bin) => bin.analyze_semantics(function_names, macro_names, handler),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Else {
|
||||
/// Analyzes the semantics of the else block.
|
||||
pub fn analyze_semantics(
|
||||
&self,
|
||||
function_names: &HashSet<String>,
|
||||
macro_names: &HashSet<String>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<(), error::Error> {
|
||||
self.block()
|
||||
.analyze_semantics(function_names, macro_names, handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl MacroStringLiteral {
|
||||
/// Analyzes the semantics of the macro string literal.
|
||||
pub fn analyze_semantics(
|
||||
&self,
|
||||
macro_names: &HashSet<String>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<(), error::Error> {
|
||||
let mut errors = Vec::new();
|
||||
for part in self.parts() {
|
||||
if let MacroStringLiteralPart::MacroUsage { identifier, .. } = part {
|
||||
if !macro_names.contains(identifier.span.str()) {
|
||||
let err = error::Error::UnresolvedMacroUsage(UnresolvedMacroUsage {
|
||||
span: identifier.span(),
|
||||
});
|
||||
handler.receive(err.clone());
|
||||
errors.push(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::option_if_let_else)]
|
||||
if let Some(err) = errors.first() {
|
||||
Err(err.clone())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Expression {
|
||||
/// Analyzes the semantics of an expression.
|
||||
pub fn analyze_semantics(
|
||||
&self,
|
||||
function_names: &HashSet<String>,
|
||||
macro_names: &HashSet<String>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<(), error::Error> {
|
||||
match self {
|
||||
Self::Primary(prim) => prim.analyze_semantics(function_names, macro_names, handler),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Primary {
|
||||
/// Analyzes the semantics of a primary expression.
|
||||
pub fn analyze_semantics(
|
||||
&self,
|
||||
function_names: &HashSet<String>,
|
||||
macro_names: &HashSet<String>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<(), error::Error> {
|
||||
match self {
|
||||
Self::FunctionCall(func) => {
|
||||
func.analyze_semantics(function_names, macro_names, handler)
|
||||
}
|
||||
Self::Lua(_) | Self::StringLiteral(_) => Ok(()),
|
||||
Self::MacroStringLiteral(literal) => literal.analyze_semantics(macro_names, handler),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionCall {
|
||||
/// Analyzes the semantics of a function call.
|
||||
pub fn analyze_semantics(
|
||||
&self,
|
||||
function_names: &HashSet<String>,
|
||||
macro_names: &HashSet<String>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<(), error::Error> {
|
||||
let mut errors = Vec::new();
|
||||
|
||||
if !function_names.contains(self.identifier().span.str()) {
|
||||
let err = error::Error::MissingFunctionDeclaration(
|
||||
MissingFunctionDeclaration::from_context(self.identifier().span(), function_names),
|
||||
);
|
||||
handler.receive(err.clone());
|
||||
errors.push(err);
|
||||
}
|
||||
|
||||
for expression in self
|
||||
.arguments()
|
||||
.iter()
|
||||
.flat_map(super::syntax::syntax_tree::ConnectedList::elements)
|
||||
{
|
||||
if let Err(err) = expression.analyze_semantics(function_names, macro_names, handler) {
|
||||
handler.receive(err.clone());
|
||||
errors.push(err);
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::option_if_let_else)]
|
||||
if let Some(err) = errors.first() {
|
||||
Err(err.clone())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AnyStringLiteral {
|
||||
/// Analyzes the semantics of any string literal.
|
||||
pub fn analyze_semantics(
|
||||
&self,
|
||||
macro_names: &HashSet<String>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<(), error::Error> {
|
||||
match self {
|
||||
Self::StringLiteral(_) => Ok(()),
|
||||
Self::MacroStringLiteral(literal) => literal.analyze_semantics(macro_names, handler),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PrimaryCondition {
|
||||
/// Analyzes the semantics of a primary condition.
|
||||
pub fn analyze_semantics(
|
||||
&self,
|
||||
function_names: &HashSet<String>,
|
||||
macro_names: &HashSet<String>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<(), error::Error> {
|
||||
match self {
|
||||
Self::Parenthesized(paren) => {
|
||||
paren.analyze_semantics(function_names, macro_names, handler)
|
||||
}
|
||||
Self::StringLiteral(_) => Ok(()),
|
||||
Self::Unary(unary) => unary.analyze_semantics(function_names, macro_names, handler),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UnaryCondition {
|
||||
/// Analyzes the semantics of an unary condition.
|
||||
pub fn analyze_semantics(
|
||||
&self,
|
||||
function_names: &HashSet<String>,
|
||||
macro_names: &HashSet<String>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<(), error::Error> {
|
||||
self.operand()
|
||||
.analyze_semantics(function_names, macro_names, handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl BinaryCondition {
|
||||
/// Analyzes the semantics of a binary condition.
|
||||
pub fn analyze_semantics(
|
||||
&self,
|
||||
function_names: &HashSet<String>,
|
||||
macro_names: &HashSet<String>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<(), error::Error> {
|
||||
let a = self
|
||||
.left_operand()
|
||||
.analyze_semantics(function_names, macro_names, handler)
|
||||
.inspect_err(|err| {
|
||||
handler.receive(err.clone());
|
||||
});
|
||||
let b = self
|
||||
.right_operand()
|
||||
.analyze_semantics(function_names, macro_names, handler)
|
||||
.inspect_err(|err| {
|
||||
handler.receive(err.clone());
|
||||
});
|
||||
if a.is_err() {
|
||||
a
|
||||
} else {
|
||||
b
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ use crate::{
|
|||
lexical::token::{Keyword, KeywordKind, Punctuation, StringLiteral, Token},
|
||||
syntax::{
|
||||
self,
|
||||
error::{InvalidArgument, ParseResult, SyntaxKind, UnexpectedSyntax},
|
||||
error::{ParseResult, SyntaxKind, UnexpectedSyntax},
|
||||
parser::{Parser, Reading},
|
||||
},
|
||||
};
|
||||
|
@ -108,22 +108,7 @@ impl<'a> Parser<'a> {
|
|||
// eat the keyword
|
||||
self.forward();
|
||||
|
||||
let namespace_name = self.parse_string_literal(handler).and_then(|name| {
|
||||
Namespace::validate_str(name.str_content().as_ref())
|
||||
.map(|()| name.clone())
|
||||
.map_err(|invalid| {
|
||||
let err = syntax::error::Error::InvalidArgument(InvalidArgument {
|
||||
message: format!(
|
||||
"Invalid characters in namespace '{}'. The following characters are not allowed in namespace definitions: '{}'",
|
||||
name.str_content(),
|
||||
invalid
|
||||
),
|
||||
span: name.span(),
|
||||
});
|
||||
handler.receive(err.clone());
|
||||
err
|
||||
})
|
||||
})?;
|
||||
let namespace_name = self.parse_string_literal(handler)?;
|
||||
|
||||
let semicolon = self.parse_punctuation(';', true, handler)?;
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
//! Execute block statement syntax tree.
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use derive_more::From;
|
||||
use enum_as_inner::EnumAsInner;
|
||||
use getset::Getters;
|
||||
|
@ -986,3 +988,91 @@ fn head_from_keyword(
|
|||
_ => unreachable!("The keyword is not a valid execute block head."),
|
||||
})
|
||||
}
|
||||
|
||||
/// Trait for the execute block head items with a [`AnyStringLiteral`] as their selector.
|
||||
pub trait ExecuteBlockHeadItem {
|
||||
/// Returns a reference to the selector of the execute block head item.
|
||||
fn selector(&self) -> &AnyStringLiteral;
|
||||
|
||||
/// Analyzes the semantics of the execute block head item.
|
||||
#[expect(clippy::missing_errors_doc)]
|
||||
fn analyze_semantics(
|
||||
&self,
|
||||
macro_names: &HashSet<String>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<(), crate::semantic::error::Error> {
|
||||
self.selector().analyze_semantics(macro_names, handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteBlockHeadItem for Align {
|
||||
fn selector(&self) -> &AnyStringLiteral {
|
||||
&self.align_selector
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteBlockHeadItem for Anchored {
|
||||
fn selector(&self) -> &AnyStringLiteral {
|
||||
&self.anchored_selector
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteBlockHeadItem for As {
|
||||
fn selector(&self) -> &AnyStringLiteral {
|
||||
&self.as_selector
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteBlockHeadItem for At {
|
||||
fn selector(&self) -> &AnyStringLiteral {
|
||||
&self.at_selector
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteBlockHeadItem for AsAt {
|
||||
fn selector(&self) -> &AnyStringLiteral {
|
||||
&self.asat_selector
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteBlockHeadItem for Facing {
|
||||
fn selector(&self) -> &AnyStringLiteral {
|
||||
&self.facing_selector
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteBlockHeadItem for In {
|
||||
fn selector(&self) -> &AnyStringLiteral {
|
||||
&self.in_selector
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteBlockHeadItem for On {
|
||||
fn selector(&self) -> &AnyStringLiteral {
|
||||
&self.on_selector
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteBlockHeadItem for Positioned {
|
||||
fn selector(&self) -> &AnyStringLiteral {
|
||||
&self.positioned_selector
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteBlockHeadItem for Rotated {
|
||||
fn selector(&self) -> &AnyStringLiteral {
|
||||
&self.rotated_selector
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteBlockHeadItem for Store {
|
||||
fn selector(&self) -> &AnyStringLiteral {
|
||||
&self.store_selector
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteBlockHeadItem for Summon {
|
||||
fn selector(&self) -> &AnyStringLiteral {
|
||||
&self.summon_selector
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,9 @@ use itertools::Itertools;
|
|||
use crate::{
|
||||
base::{
|
||||
log::{Message, Severity, SourceCodeDisplay},
|
||||
source_file::{SourceElement, Span},
|
||||
source_file::Span,
|
||||
},
|
||||
syntax::syntax_tree::expression::Expression,
|
||||
semantic::error::UnexpectedExpression,
|
||||
};
|
||||
|
||||
use super::transpiler::FunctionData;
|
||||
|
@ -20,7 +20,7 @@ use super::transpiler::FunctionData;
|
|||
#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)]
|
||||
pub enum TranspileError {
|
||||
#[error(transparent)]
|
||||
MissingFunctionDeclaration(#[from] MissingFunctionDeclaration),
|
||||
MissingFunctionDeclaration(#[from] TranspileMissingFunctionDeclaration),
|
||||
#[error(transparent)]
|
||||
UnexpectedExpression(#[from] UnexpectedExpression),
|
||||
#[error("Lua code evaluation is disabled.")]
|
||||
|
@ -36,14 +36,14 @@ pub type TranspileResult<T> = Result<T, TranspileError>;
|
|||
|
||||
/// An error that occurs when a function declaration is missing.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Getters)]
|
||||
pub struct MissingFunctionDeclaration {
|
||||
pub struct TranspileMissingFunctionDeclaration {
|
||||
#[get = "pub"]
|
||||
span: Span,
|
||||
#[get = "pub"]
|
||||
alternatives: Vec<FunctionData>,
|
||||
}
|
||||
|
||||
impl MissingFunctionDeclaration {
|
||||
impl TranspileMissingFunctionDeclaration {
|
||||
pub(super) fn from_context(
|
||||
identifier_span: Span,
|
||||
functions: &BTreeMap<(String, String), FunctionData>,
|
||||
|
@ -73,7 +73,7 @@ impl MissingFunctionDeclaration {
|
|||
}
|
||||
}
|
||||
|
||||
impl Display for MissingFunctionDeclaration {
|
||||
impl Display for TranspileMissingFunctionDeclaration {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let message = format!(
|
||||
"no matching function declaration found for invocation of function `{}`",
|
||||
|
@ -102,7 +102,7 @@ impl Display for MissingFunctionDeclaration {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for MissingFunctionDeclaration {}
|
||||
impl std::error::Error for TranspileMissingFunctionDeclaration {}
|
||||
|
||||
/// An error that occurs when a function declaration is missing.
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
|
@ -144,28 +144,6 @@ impl LuaRuntimeError {
|
|||
}
|
||||
}
|
||||
|
||||
/// An error that occurs when a function declaration is missing.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UnexpectedExpression(pub Expression);
|
||||
|
||||
impl Display for UnexpectedExpression {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
Message::new(Severity::Error, "encountered unexpected expression")
|
||||
)?;
|
||||
|
||||
write!(
|
||||
f,
|
||||
"\n{}",
|
||||
SourceCodeDisplay::new(&self.0.span(), Option::<u8>::None)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for UnexpectedExpression {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ConflictingFunctionNames {
|
||||
pub definition: Span,
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
use chksum_md5 as md5;
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
iter,
|
||||
sync::RwLock,
|
||||
};
|
||||
|
||||
|
@ -15,6 +14,7 @@ use crate::{
|
|||
source_file::{SourceElement, Span},
|
||||
Handler,
|
||||
},
|
||||
semantic::error::UnexpectedExpression,
|
||||
syntax::syntax_tree::{
|
||||
declaration::{Declaration, ImportItems},
|
||||
expression::{Expression, FunctionCall, Primary},
|
||||
|
@ -24,10 +24,10 @@ use crate::{
|
|||
Statement,
|
||||
},
|
||||
},
|
||||
transpile::error::{ConflictingFunctionNames, MissingFunctionDeclaration},
|
||||
transpile::error::{ConflictingFunctionNames, TranspileMissingFunctionDeclaration},
|
||||
};
|
||||
|
||||
use super::error::{TranspileError, TranspileResult, UnexpectedExpression};
|
||||
use super::error::{TranspileError, TranspileResult};
|
||||
|
||||
/// A transpiler for `Shulkerscript`.
|
||||
#[derive(Debug)]
|
||||
|
@ -43,6 +43,7 @@ pub struct Transpiler {
|
|||
pub(super) struct FunctionData {
|
||||
pub(super) namespace: String,
|
||||
pub(super) identifier_span: Span,
|
||||
pub(super) parameters: Vec<String>,
|
||||
pub(super) statements: Vec<Statement>,
|
||||
pub(super) public: bool,
|
||||
pub(super) annotations: HashMap<String, Option<String>>,
|
||||
|
@ -127,7 +128,7 @@ impl Transpiler {
|
|||
&mut self,
|
||||
declaration: &Declaration,
|
||||
namespace: &Namespace,
|
||||
_handler: &impl Handler<base::Error>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) {
|
||||
let program_identifier = declaration.span().source_file().identifier().clone();
|
||||
match declaration {
|
||||
|
@ -153,6 +154,15 @@ impl Transpiler {
|
|||
FunctionData {
|
||||
namespace: namespace.namespace_name().str_content().to_string(),
|
||||
identifier_span: identifier_span.clone(),
|
||||
parameters: function
|
||||
.parameters()
|
||||
.as_ref()
|
||||
.map(|l| {
|
||||
l.elements()
|
||||
.map(|i| i.span.str().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
statements,
|
||||
public: function.is_public(),
|
||||
annotations,
|
||||
|
@ -167,10 +177,13 @@ impl Transpiler {
|
|||
let mut aliases = self.aliases.write().unwrap();
|
||||
|
||||
match import.items() {
|
||||
ImportItems::All(_) => todo!("Importing all items is not yet supported."),
|
||||
ImportItems::All(_) => {
|
||||
handler.receive(base::Error::Other(
|
||||
"Importing all items is not yet supported.".to_string(),
|
||||
));
|
||||
}
|
||||
ImportItems::Named(list) => {
|
||||
let items = iter::once(list.first())
|
||||
.chain(list.rest().iter().map(|(_, ident)| ident));
|
||||
let items = list.elements();
|
||||
|
||||
for item in items {
|
||||
let name = item.span.str();
|
||||
|
@ -244,7 +257,7 @@ impl Transpiler {
|
|||
})
|
||||
.ok_or_else(|| {
|
||||
let error = TranspileError::MissingFunctionDeclaration(
|
||||
MissingFunctionDeclaration::from_context(
|
||||
TranspileMissingFunctionDeclaration::from_context(
|
||||
identifier_span.clone(),
|
||||
&functions,
|
||||
),
|
||||
|
@ -252,6 +265,7 @@ impl Transpiler {
|
|||
handler.receive(error.clone());
|
||||
error
|
||||
})?;
|
||||
|
||||
function_data.statements.clone()
|
||||
};
|
||||
let commands = self.transpile_function(&statements, program_identifier, handler)?;
|
||||
|
@ -266,7 +280,7 @@ impl Transpiler {
|
|||
})
|
||||
.ok_or_else(|| {
|
||||
let error = TranspileError::MissingFunctionDeclaration(
|
||||
MissingFunctionDeclaration::from_context(
|
||||
TranspileMissingFunctionDeclaration::from_context(
|
||||
identifier_span.clone(),
|
||||
&functions,
|
||||
),
|
||||
|
@ -329,7 +343,7 @@ impl Transpiler {
|
|||
.or_else(|| alias_query.and_then(|q| locations.get(&q).filter(|(_, p)| *p)))
|
||||
.ok_or_else(|| {
|
||||
let error = TranspileError::MissingFunctionDeclaration(
|
||||
MissingFunctionDeclaration::from_context(
|
||||
TranspileMissingFunctionDeclaration::from_context(
|
||||
identifier_span.clone(),
|
||||
&self.functions.read().unwrap(),
|
||||
),
|
||||
|
|
Loading…
Reference in New Issue