diff --git a/Cargo.toml b/Cargo.toml index 9b997de..4a8fa30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/base/error.rs b/src/base/error.rs index ec14dba..7a61661 100644 --- a/src/base/error.rs +++ b/src/base/error.rs @@ -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), diff --git a/src/lib.rs b/src/lib.rs index ec35d2d..e951517 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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) } diff --git a/src/semantic/error.rs b/src/semantic/error.rs new file mode 100644 index 0000000..14601b6 --- /dev/null +++ b/src/semantic/error.rs @@ -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, +} + +impl MissingFunctionDeclaration { + pub(super) fn from_context(identifier_span: Span, functions: &HashSet) -> 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::>(); + + 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::::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::::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::::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 {} diff --git a/src/semantic/mod.rs b/src/semantic/mod.rs new file mode 100644 index 0000000..0873d7e --- /dev/null +++ b/src/semantic/mod.rs @@ -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, + ) -> 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, +) -> Result, 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, + ) -> 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, + handler: &impl Handler, + ) -> 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, + handler: &impl Handler, + ) -> 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, + macro_names: &HashSet, + handler: &impl Handler, + ) -> 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, + macro_names: &HashSet, + handler: &impl Handler, + ) -> 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, + macro_names: &HashSet, + handler: &impl Handler, + ) -> 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, + macro_names: &HashSet, + handler: &impl Handler, + ) -> 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, + macro_names: &HashSet, + handler: &impl Handler, + ) -> 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, + macro_names: &HashSet, + handler: &impl Handler, + ) -> 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, + macro_names: &HashSet, + handler: &impl Handler, + ) -> 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, + macro_names: &HashSet, + handler: &impl Handler, + ) -> 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, + macro_names: &HashSet, + handler: &impl Handler, + ) -> 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, + macro_names: &HashSet, + handler: &impl Handler, + ) -> 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, + macro_names: &HashSet, + handler: &impl Handler, + ) -> 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, + handler: &impl Handler, + ) -> 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, + macro_names: &HashSet, + handler: &impl Handler, + ) -> 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, + macro_names: &HashSet, + handler: &impl Handler, + ) -> 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, + macro_names: &HashSet, + handler: &impl Handler, + ) -> 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, + handler: &impl Handler, + ) -> 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, + macro_names: &HashSet, + handler: &impl Handler, + ) -> 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, + macro_names: &HashSet, + handler: &impl Handler, + ) -> 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, + macro_names: &HashSet, + handler: &impl Handler, + ) -> 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 + } + } +} diff --git a/src/syntax/syntax_tree/program.rs b/src/syntax/syntax_tree/program.rs index e487d2a..febff77 100644 --- a/src/syntax/syntax_tree/program.rs +++ b/src/syntax/syntax_tree/program.rs @@ -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)?; diff --git a/src/syntax/syntax_tree/statement/execute_block.rs b/src/syntax/syntax_tree/statement/execute_block.rs index 46ccffd..ef19b7f 100644 --- a/src/syntax/syntax_tree/statement/execute_block.rs +++ b/src/syntax/syntax_tree/statement/execute_block.rs @@ -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, + handler: &impl Handler, + ) -> 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 + } +} diff --git a/src/transpile/error.rs b/src/transpile/error.rs index 24a50a8..7fb9d16 100644 --- a/src/transpile/error.rs +++ b/src/transpile/error.rs @@ -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 = Result; /// 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, } -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::::None) - ) - } -} - -impl std::error::Error for UnexpectedExpression {} - #[derive(Debug, Clone, PartialEq, Eq)] pub struct ConflictingFunctionNames { pub definition: Span, diff --git a/src/transpile/transpiler.rs b/src/transpile/transpiler.rs index a059ff9..c6e9502 100644 --- a/src/transpile/transpiler.rs +++ b/src/transpile/transpiler.rs @@ -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, pub(super) statements: Vec, pub(super) public: bool, pub(super) annotations: HashMap>, @@ -127,7 +128,7 @@ impl Transpiler { &mut self, declaration: &Declaration, namespace: &Namespace, - _handler: &impl Handler, + handler: &impl Handler, ) { 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::>() + }) + .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(), ),