diff --git a/src/semantic/error.rs b/src/semantic/error.rs index adac477..c7f75fa 100644 --- a/src/semantic/error.rs +++ b/src/semantic/error.rs @@ -2,10 +2,7 @@ #![allow(missing_docs)] -use std::{collections::HashSet, fmt::Display}; - -use getset::Getters; -use itertools::Itertools as _; +use std::fmt::Display; use crate::{ base::{ @@ -14,6 +11,10 @@ use crate::{ }, lexical::token::StringLiteral, syntax::syntax_tree::expression::Expression, + transpile::error::{ + AssignmentError, IllegalIndexing, MismatchedTypes, MissingFunctionDeclaration, + UnknownIdentifier, + }, }; #[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)] @@ -31,77 +32,20 @@ pub enum Error { UnresolvedMacroUsage(#[from] UnresolvedMacroUsage), #[error(transparent)] IncompatibleFunctionAnnotation(#[from] IncompatibleFunctionAnnotation), + #[error(transparent)] + IllegalIndexing(#[from] IllegalIndexing), + #[error(transparent)] + MismatchedTypes(#[from] MismatchedTypes), + #[error(transparent)] + UnknownIdentifier(#[from] UnknownIdentifier), + #[error(transparent)] + AssignmentError(#[from] AssignmentError), + #[error("Lua is disabled, but a Lua function was used.")] + LuaDisabled, + #[error("Other: {0}")] + Other(String), } -// TODO: remove duplicate error (also in transpile) -/// 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 { - #[expect(dead_code)] - 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); diff --git a/src/semantic/mod.rs b/src/semantic/mod.rs index 2bd9222..783fea5 100644 --- a/src/semantic/mod.rs +++ b/src/semantic/mod.rs @@ -2,30 +2,37 @@ #![allow(clippy::missing_errors_doc)] -use std::collections::HashSet; - -use error::{IncompatibleFunctionAnnotation, InvalidNamespaceName}; - use crate::{ base::{self, source_file::SourceElement as _, Handler}, - lexical::token::MacroStringLiteral, + lexical::token::{KeywordKind, MacroStringLiteral, MacroStringLiteralPart}, syntax::syntax_tree::{ - declaration::{Declaration, Function, ImportItems}, - expression::{Expression, FunctionCall, Parenthesized, Primary}, + declaration::{Declaration, Function, FunctionVariableType, ImportItems}, + expression::{Binary, BinaryOperator, Expression, LuaCode, PrefixOperator, Primary}, program::{Namespace, ProgramFile}, statement::{ execute_block::{ Conditional, Else, ExecuteBlock, ExecuteBlockHead, ExecuteBlockHeadItem as _, ExecuteBlockTail, }, - Block, Grouping, Run, Semicolon, SemicolonStatement, Statement, VariableDeclaration, + Assignment, AssignmentDestination, Block, Grouping, Run, Semicolon, SemicolonStatement, + Statement, VariableDeclaration, }, AnyStringLiteral, }, + transpile::{ + error::{AssignmentError, IllegalIndexing, MismatchedTypes, UnknownIdentifier}, + expression::{ExpectedType, ValueType}, + }, }; pub mod error; +mod scope; +use error::{IncompatibleFunctionAnnotation, InvalidNamespaceName, UnexpectedExpression}; +pub use scope::{SemanticScope, VariableType}; + +use super::syntax::syntax_tree::ConnectedList; + impl ProgramFile { /// Analyzes the semantics of the program. pub fn analyze_semantics( @@ -35,84 +42,31 @@ impl ProgramFile { self.namespace().analyze_semantics(handler)?; let mut errs = Vec::new(); - let function_names = extract_all_function_names(self.declarations(), handler)?; + let scope = get_global_scope(self.declarations()); for declaration in self.declarations() { - if let Err(err) = declaration.analyze_semantics(&function_names, handler) { + if let Err(err) = declaration.analyze_semantics(&scope, handler) { errs.push(err); } } - #[expect(clippy::option_if_let_else)] - if let Some(err) = errs.first() { - Err(err.clone()) - } else { - Ok(()) - } + errs.into_iter().next().map_or(Ok(()), Err) } } -fn extract_all_function_names( - declarations: &[Declaration], - handler: &impl Handler, -) -> Result, error::Error> { - let mut function_names = HashSet::new(); - let mut errs = Vec::new(); +fn get_global_scope(declarations: &[Declaration]) -> SemanticScope<'static> { + let scope = SemanticScope::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(_) => {} - } + declaration.add_to_scope(&scope); } - #[expect(clippy::option_if_let_else)] - if let Some(err) = errs.first() { - Err(err.clone()) - } else { - Ok(function_names) - } + scope } impl Namespace { /// Analyzes the semantics of the namespace. - pub fn analyze_semantics( - &self, - handler: &impl Handler, - ) -> Result<(), error::Error> { + 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 { @@ -126,27 +80,74 @@ impl Namespace { } impl Declaration { + fn add_to_scope(&self, scope: &SemanticScope) { + match self { + Self::Function(func) => { + let name = func.identifier(); + scope.set_variable(name.span.str(), VariableType::Function); + } + Self::Import(imp) => match imp.items() { + ImportItems::All(_) => {} + ImportItems::Named(items) => { + for item in items.elements() { + if scope.get_variable(item.span.str()) != Some(VariableType::Function) { + scope.set_variable(item.span.str(), VariableType::Function); + } + } + } + }, + Self::Tag(_) => {} + } + } + /// Analyzes the semantics of the declaration. - pub fn analyze_semantics( + fn analyze_semantics( &self, - function_names: &HashSet, + scope: &SemanticScope, handler: &impl Handler, ) -> Result<(), error::Error> { match self { - Self::Function(func) => func.analyze_semantics(function_names, handler), - Self::Import(_) | Self::Tag(_) => Ok(()), + Self::Function(func) => func.analyze_semantics(scope, handler), + + Self::Import(imp) => match imp.items() { + ImportItems::All(_) => { + let err = error::Error::Other( + "Importing all items is not yet supported.".to_string(), + ); + handler.receive(err.clone()); + Err(err) + } + ImportItems::Named(items) => { + for item in items.elements() { + if scope.get_variable(item.span.str()) == Some(VariableType::Function) { + let err = error::Error::from(error::ConflictingFunctionNames { + name: item.span.str().to_string(), + definition: item.span(), + }); + handler.receive(err.clone()); + return Err(err); + } + scope.set_variable(item.span.str(), VariableType::Function); + } + + Ok(()) + } + }, + + Self::Tag(_) => Ok(()), } } } impl Function { - /// Analyzes the semantics of the function. - pub fn analyze_semantics( + fn analyze_semantics( &self, - function_names: &HashSet, + scope: &SemanticScope, handler: &impl Handler, ) -> Result<(), error::Error> { - let macro_names = if let Some(parameters) = self.parameters() { + let child_scope = SemanticScope::with_parent(scope); + + if let Some(parameters) = self.parameters().as_ref().map(ConnectedList::elements) { if let Some(incompatible) = self .annotations() .iter() @@ -154,7 +155,7 @@ impl Function { { let err = error::Error::IncompatibleFunctionAnnotation(IncompatibleFunctionAnnotation { - span: incompatible.assignment().identifier.span(), + span: incompatible.assignment().identifier.span.clone(), reason: "functions with the `tick` or `load` annotation cannot have parameters" .to_string(), @@ -163,70 +164,74 @@ impl Function { return Err(err); } - parameters - .elements() - .map(|el| el.identifier().span.str().to_string()) - .collect() - } else { - HashSet::new() - }; + for param in parameters { + let name = param.identifier().span.str(); + let var = match param.variable_type() { + FunctionVariableType::Boolean(_) => VariableType::BooleanStorage, + FunctionVariableType::Integer(_) => VariableType::ScoreboardValue, + FunctionVariableType::Macro(_) => VariableType::MacroParameter, + }; + child_scope.set_variable(name, var); + } + } - self.block() - .analyze_semantics(function_names, ¯o_names, handler) + self.block().analyze_semantics(&child_scope, handler)?; + + Ok(()) } } impl Block { - /// Analyzes the semantics of a block. - pub fn analyze_semantics( + fn analyze_semantics( &self, - function_names: &HashSet, - macro_names: &HashSet, + scope: &SemanticScope, handler: &impl Handler, ) -> Result<(), error::Error> { let mut errs = Vec::new(); - for statement in &self.statements { - if let Err(err) = match statement { - Statement::Block(block) => { - block.analyze_semantics(function_names, macro_names, handler) - } - Statement::DocComment(_) | Statement::LiteralCommand(_) => Ok(()), - 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) - } - } { + + for statement in self.statements() { + if let Err(err) = statement.analyze_semantics(scope, handler) { errs.push(err); - }; + } } - #[expect(clippy::option_if_let_else)] - if let Some(err) = errs.first() { - Err(err.clone()) - } else { - Ok(()) + errs.first().map_or(Ok(()), |err| Err(err.clone())) + } +} + +impl Statement { + fn analyze_semantics( + &self, + scope: &SemanticScope, + handler: &impl Handler, + ) -> Result<(), error::Error> { + match self { + Self::Block(block) => { + let child_scope = SemanticScope::with_parent(scope); + block.analyze_semantics(&child_scope, handler) + } + Self::DocComment(_) | Self::LiteralCommand(_) => Ok(()), + Self::ExecuteBlock(ex) => ex.analyze_semantics(scope, handler), + Self::Grouping(group) => { + let child_scope = SemanticScope::with_parent(scope); + group.analyze_semantics(&child_scope, handler) + } + Self::Run(run) => run.analyze_semantics(scope, handler), + Self::Semicolon(sem) => sem.analyze_semantics(scope, handler), } } } impl ExecuteBlock { - /// Analyzes the semantics of the execute block. - pub fn analyze_semantics( + fn analyze_semantics( &self, - function_names: &HashSet, - macro_names: &HashSet, + scope: &SemanticScope, handler: &impl Handler, ) -> Result<(), error::Error> { match self { Self::HeadTail(head, tail) => { - let head_res = head.analyze_semantics(function_names, macro_names, handler); - let tail_res = tail.analyze_semantics(function_names, macro_names, handler); + let head_res = head.analyze_semantics(scope, handler); + let tail_res = tail.analyze_semantics(scope, handler); if head_res.is_err() { head_res @@ -235,9 +240,11 @@ impl ExecuteBlock { } } Self::IfElse(cond, then, el) => { - let cond_res = cond.analyze_semantics(function_names, macro_names, handler); - let then_res = then.analyze_semantics(function_names, macro_names, handler); - let else_res = el.analyze_semantics(function_names, macro_names, handler); + let cond_res = cond.analyze_semantics(scope, handler); + let then_scope = SemanticScope::with_parent(scope); + let then_res = then.analyze_semantics(&then_scope, handler); + let el_scope = SemanticScope::with_parent(scope); + let else_res = el.analyze_semantics(&el_scope, handler); if cond_res.is_err() { cond_res @@ -251,351 +258,547 @@ impl ExecuteBlock { } } -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.statement() { - SemicolonStatement::Expression(expr) => { - expr.analyze_semantics(function_names, macro_names, handler) - } - SemicolonStatement::VariableDeclaration(decl) => { - decl.analyze_semantics(function_names, macro_names, handler) - } - SemicolonStatement::Assignment(_assignment) => { - // TODO: correctly analyze the semantics of the assignment - Ok(()) - } - } - } -} - impl ExecuteBlockHead { - /// Analyzes the semantics of the execute block head. - pub fn analyze_semantics( + fn analyze_semantics( &self, - function_names: &HashSet, - macro_names: &HashSet, + scope: &SemanticScope, 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), + Self::Align(align) => align.analyze_semantics(scope, handler), + Self::Anchored(anchored) => anchored.analyze_semantics(scope, handler), + Self::As(r#as) => r#as.analyze_semantics(scope, handler), + Self::At(at) => at.analyze_semantics(scope, handler), + Self::AsAt(asat) => asat.analyze_semantics(scope, handler), + Self::Conditional(cond) => cond.analyze_semantics(scope, handler), + Self::Facing(facing) => facing.analyze_semantics(scope, handler), + Self::In(r#in) => r#in.analyze_semantics(scope, handler), + Self::On(on) => on.analyze_semantics(scope, handler), + Self::Positioned(pos) => pos.analyze_semantics(scope, handler), + Self::Rotated(rot) => rot.analyze_semantics(scope, handler), + Self::Store(store) => store.analyze_semantics(scope, handler), + Self::Summon(summon) => summon.analyze_semantics(scope, handler), } } } impl ExecuteBlockTail { - /// Analyzes the semantics of the execute block tail. - pub fn analyze_semantics( + fn analyze_semantics( &self, - function_names: &HashSet, - macro_names: &HashSet, + scope: &SemanticScope, 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), + Self::Block(block) => { + let child_scope = SemanticScope::with_parent(scope); + block.analyze_semantics(&child_scope, handler) + } + Self::ExecuteBlock(_, ex) => ex.analyze_semantics(scope, handler), } } } +impl Else { + fn analyze_semantics( + &self, + scope: &SemanticScope, + handler: &impl Handler, + ) -> Result<(), error::Error> { + let child_scope = SemanticScope::with_parent(scope); + self.block().analyze_semantics(&child_scope, handler) + } +} + impl Conditional { /// Analyzes the semantics of the conditional. pub fn analyze_semantics( &self, - function_names: &HashSet, - macro_names: &HashSet, + scope: &SemanticScope, handler: &impl Handler, ) -> Result<(), error::Error> { - self.condition() - .analyze_semantics(function_names, macro_names, handler) + self.condition().analyze_semantics(scope, handler) } } -impl Parenthesized { - /// Analyzes the semantics of the parenthesized condition. - pub fn analyze_semantics( +impl Grouping { + fn analyze_semantics( &self, - function_names: &HashSet, - macro_names: &HashSet, + scope: &SemanticScope, handler: &impl Handler, ) -> Result<(), error::Error> { - self.expression() - .analyze_semantics(function_names, macro_names, handler) + self.block().analyze_semantics(scope, handler) } } -impl Else { - /// Analyzes the semantics of the else block. - pub fn analyze_semantics( +impl Run { + fn analyze_semantics( &self, - function_names: &HashSet, - macro_names: &HashSet, + scope: &SemanticScope, handler: &impl Handler, ) -> Result<(), error::Error> { - self.block() - .analyze_semantics(function_names, macro_names, handler) + self.expression().analyze_semantics(scope, handler) } } -impl MacroStringLiteral { - /// Analyzes the semantics of the macro string literal. - pub fn analyze_semantics( +impl Semicolon { + fn analyze_semantics( &self, - _macro_names: &HashSet, - _handler: &impl Handler, + scope: &SemanticScope, + handler: &impl Handler, ) -> Result<(), error::Error> { - // let mut errors = Vec::new(); - // TODO: allow macro string literals to also contain other variables - // 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); - // } - // } - // } + match self.statement() { + SemicolonStatement::Assignment(assignment) => { + assignment.analyze_semantics(scope, handler) + } + SemicolonStatement::Expression(expr) => expr.analyze_semantics(scope, handler), + SemicolonStatement::VariableDeclaration(decl) => decl.analyze_semantics(scope, handler), + } + } +} + +impl Assignment { + fn analyze_semantics( + &self, + scope: &SemanticScope, + handler: &impl Handler, + ) -> Result<(), error::Error> { + let variable_type = match self.destination() { + AssignmentDestination::Identifier(ident) => scope.get_variable(ident.span.str()), + AssignmentDestination::Indexed(ident, _, index, _) => { + let var = scope.get_variable(ident.span.str()); + index.analyze_semantics(scope, handler)?; + let valid_index = match var { + Some(VariableType::Tag | VariableType::Scoreboard) => (!index + .can_yield_type_semantics(ValueType::String, scope)) + .then_some(ExpectedType::String), + Some(VariableType::ScoreboardArray | VariableType::BooleanStorageArray) => { + (!index.can_yield_type_semantics(ValueType::Integer, scope)) + .then_some(ExpectedType::Integer) + } + _ => None, + }; + if let Some(expected) = valid_index { + let err = error::Error::IllegalIndexing(IllegalIndexing { + expression: index.span(), + reason: + crate::transpile::error::IllegalIndexingReason::InvalidComptimeType { + expected, + }, + }); + handler.receive(err.clone()); + return Err(err); + } + var + } + }; + + if let Some(variable_type) = variable_type { + let expected = match variable_type { + VariableType::BooleanStorage | VariableType::Tag => ValueType::Boolean, + VariableType::ScoreboardValue => ValueType::Integer, + _ => { + let err = error::Error::AssignmentError(AssignmentError { + identifier: self.destination().span(), + message: "cannot assign to this type".to_string(), + }); + handler.receive(err.clone()); + return Err(err); + } + }; + + if !self.expression().can_yield_type_semantics(expected, scope) { + let err = error::Error::MismatchedTypes(MismatchedTypes { + expected_type: expected.into(), + expression: self.expression().span(), + }); + handler.receive(err.clone()); + return Err(err); + } + } else { + let err = error::Error::UnknownIdentifier(UnknownIdentifier { + identifier: self.destination().span(), + }); + handler.receive(err.clone()); + return Err(err); + } + self.expression().analyze_semantics(scope, handler) + } +} + +impl VariableDeclaration { + fn analyze_semantics( + &self, + scope: &SemanticScope, + handler: &impl Handler, + ) -> Result<(), error::Error> { + let name = self.identifier().span.str(); + let (var, expected) = match self { + Self::Array(_) => match self.variable_type().keyword { + KeywordKind::Bool => (VariableType::BooleanStorageArray, ExpectedType::Boolean), + KeywordKind::Int => (VariableType::ScoreboardArray, ExpectedType::Integer), + _ => unreachable!("variable type is not a valid type"), + }, + Self::Score(_) => (VariableType::Scoreboard, ExpectedType::Integer), + Self::Tag(_) => (VariableType::Tag, ExpectedType::Boolean), + Self::Single(_) => match self.variable_type().keyword { + KeywordKind::Bool => (VariableType::BooleanStorage, ExpectedType::Boolean), + KeywordKind::Int => (VariableType::ScoreboardValue, ExpectedType::Integer), + _ => unreachable!("variable type is not a valid type"), + }, + }; + scope.set_variable(name, var); + let assignment = match self { + Self::Array(arr) => arr.assignment().as_ref(), + Self::Score(scr) => scr.assignment().as_ref(), + Self::Tag(tag) => { + if let Some((target, assignment)) = tag.target_assignment() { + target.analyze_semantics(scope, handler)?; + Some(assignment) + } else { + None + } + } + Self::Single(single) => single.assignment().as_ref(), + }; + if let Some(assignment) = assignment { + let expected = match var { + VariableType::BooleanStorage | VariableType::Tag => ValueType::Boolean, + VariableType::ScoreboardValue => ValueType::Integer, + _ => { + let err = error::Error::MismatchedTypes(MismatchedTypes { + expected_type: expected, + expression: assignment.expression().span(), + }); + handler.receive(err.clone()); + return Err(err); + } + }; + if !assignment + .expression() + .can_yield_type_semantics(expected, scope) + { + let err = error::Error::MismatchedTypes(MismatchedTypes { + expected_type: expected.into(), + expression: assignment.expression().span(), + }); + handler.receive(err.clone()); + return Err(err); + } + assignment.expression().analyze_semantics(scope, handler)?; + } - // #[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( + fn analyze_semantics( &self, - function_names: &HashSet, - macro_names: &HashSet, + scope: &SemanticScope, handler: &impl Handler, ) -> Result<(), error::Error> { match self { - Self::Primary(prim) => prim.analyze_semantics(function_names, macro_names, handler), - Self::Binary(_bin) => { - // TODO: correctly analyze the semantics of the binary expression - Ok(()) - } + Self::Primary(primary) => primary.analyze_semantics(scope, handler), + Self::Binary(binary) => binary.analyze_semantics(scope, handler), + } + } + + #[must_use] + fn can_yield_type_semantics(&self, expected: ValueType, scope: &SemanticScope) -> bool { + match self { + Self::Primary(primary) => primary.can_yield_type_semantics(expected, scope), + Self::Binary(binary) => binary.can_yield_type_semantics(expected, scope), } } } impl Primary { - /// Analyzes the semantics of a primary expression. - pub fn analyze_semantics( + fn analyze_semantics( &self, - function_names: &HashSet, - macro_names: &HashSet, + scope: &SemanticScope, handler: &impl Handler, ) -> Result<(), error::Error> { match self { - Self::FunctionCall(func) => { - func.analyze_semantics(function_names, macro_names, handler) + Self::Identifier(ident) => { + if scope.get_variable(ident.span.str()).is_none() { + let err = error::Error::UnknownIdentifier(UnknownIdentifier { + identifier: ident.span.clone(), + }); + handler.receive(err.clone()); + Err(err) + } else { + Ok(()) + } } - Self::Lua(_) | Self::StringLiteral(_) | Self::Integer(_) | Self::Boolean(_) => Ok(()), - Self::MacroStringLiteral(literal) => literal.analyze_semantics(macro_names, handler), - Self::Identifier(_) | Self::Parenthesized(_) | Self::Prefix(_) | Self::Indexed(_) => { - // TODO: correctly analyze the semantics of the primary expression - Ok(()) + Self::Boolean(_) | Self::Integer(_) | Self::StringLiteral(_) => Ok(()), + Self::MacroStringLiteral(lit) => lit.analyze_semantics(scope, handler), + Self::FunctionCall(call) => { + if scope.get_variable(call.identifier().span.str()) == Some(VariableType::Function) + { + Ok(()) + } else { + let err = error::Error::UnknownIdentifier(UnknownIdentifier { + identifier: call.identifier().span.clone(), + }); + handler.receive(err.clone()); + Err(err) + } + } + Self::Indexed(indexed) => { + // TODO: check if target is indexable and can be indexed with given type + indexed.object().analyze_semantics(scope, handler)?; + indexed.index().analyze_semantics(scope, handler) + } + Self::Parenthesized(expr) => expr.analyze_semantics(scope, handler), + Self::Prefix(prefixed) => match prefixed.operator() { + PrefixOperator::LogicalNot(_) => { + if prefixed + .operand() + .can_yield_type_semantics(ValueType::Boolean, scope) + { + prefixed.operand().analyze_semantics(scope, handler) + } else { + let err = error::Error::MismatchedTypes(MismatchedTypes { + expected_type: ExpectedType::Boolean, + expression: prefixed.operand().span(), + }); + handler.receive(err.clone()); + Err(err) + } + } + PrefixOperator::Negate(_) => { + if prefixed + .operand() + .can_yield_type_semantics(ValueType::Integer, scope) + { + prefixed.operand().analyze_semantics(scope, handler) + } else { + let err = error::Error::MismatchedTypes(MismatchedTypes { + expected_type: ExpectedType::Integer, + expression: prefixed.operand().span(), + }); + handler.receive(err.clone()); + Err(err) + } + } + }, + Self::Lua(lua) => lua.analyze_semantics(scope, handler), + } + } + + #[must_use] + fn can_yield_type_semantics(&self, expected: ValueType, scope: &SemanticScope) -> bool { + match self { + Self::Boolean(_) => expected == ValueType::Boolean, + Self::Integer(_) => expected == ValueType::Integer, + Self::StringLiteral(_) | Self::MacroStringLiteral(_) => expected == ValueType::String, + Self::FunctionCall(_) => matches!(expected, ValueType::Boolean | ValueType::Integer), + Self::Indexed(indexed) => match indexed.object().as_ref() { + Self::Identifier(ident) => { + let var = scope.get_variable(ident.span.str()); + match (var, expected) { + (Some(VariableType::BooleanStorageArray), ValueType::Boolean) + | (Some(VariableType::ScoreboardArray), ValueType::Integer) => indexed + .index() + .can_yield_type_semantics(ValueType::Integer, scope), + (Some(VariableType::Tag), ValueType::Boolean) + | (Some(VariableType::Scoreboard), ValueType::Integer) => indexed + .index() + .can_yield_type_semantics(ValueType::String, scope), + _ => false, + } + } + _ => false, + }, + Self::Identifier(ident) => match scope.get_variable(ident.span.str()) { + Some(VariableType::BooleanStorage) => expected == ValueType::Boolean, + Some(VariableType::ScoreboardValue) => expected == ValueType::Integer, + Some(VariableType::Tag) => expected == ValueType::String, + _ => false, + }, + Self::Prefix(prefixed) => match prefixed.operator() { + PrefixOperator::LogicalNot(_) => prefixed + .operand() + .can_yield_type_semantics(ValueType::Boolean, scope), + PrefixOperator::Negate(_) => prefixed + .operand() + .can_yield_type_semantics(ValueType::Integer, scope), + }, + Self::Parenthesized(parenthesized) => { + parenthesized.can_yield_type_semantics(expected, scope) + } + Self::Lua(lua) => lua.can_yield_type_semantics(expected), + } + } +} + +impl Binary { + fn analyze_semantics( + &self, + scope: &SemanticScope, + handler: &impl Handler, + ) -> Result<(), error::Error> { + match self.operator() { + BinaryOperator::Add(_) + | BinaryOperator::Subtract(_) + | BinaryOperator::Multiply(_) + | BinaryOperator::Divide(_) + | BinaryOperator::Modulo(_) => { + if self + .left_operand() + .can_yield_type_semantics(ValueType::Integer, scope) + && self + .right_operand() + .can_yield_type_semantics(ValueType::Integer, scope) + { + self.left_operand().analyze_semantics(scope, handler)?; + self.right_operand().analyze_semantics(scope, handler) + } else { + let err = error::Error::MismatchedTypes(MismatchedTypes { + expected_type: ExpectedType::Integer, + expression: self.span(), + }); + handler.receive(err.clone()); + Err(err) + } + } + BinaryOperator::Equal(..) + | BinaryOperator::NotEqual(..) + | BinaryOperator::GreaterThan(_) + | BinaryOperator::GreaterThanOrEqual(..) + | BinaryOperator::LessThan(_) + | BinaryOperator::LessThanOrEqual(..) => { + self.left_operand().analyze_semantics(scope, handler)?; + self.right_operand().analyze_semantics(scope, handler) + } + BinaryOperator::LogicalAnd(..) | BinaryOperator::LogicalOr(..) => { + if self + .left_operand() + .can_yield_type_semantics(ValueType::Boolean, scope) + && self + .right_operand() + .can_yield_type_semantics(ValueType::Boolean, scope) + { + self.left_operand().analyze_semantics(scope, handler)?; + self.right_operand().analyze_semantics(scope, handler) + } else { + let err = error::Error::MismatchedTypes(MismatchedTypes { + expected_type: ExpectedType::Boolean, + expression: self.span(), + }); + handler.receive(err.clone()); + Err(err) + } + } + } + } + + #[must_use] + fn can_yield_type_semantics(&self, expected: ValueType, scope: &SemanticScope) -> bool { + match self.operator() { + BinaryOperator::Add(_) + | BinaryOperator::Subtract(_) + | BinaryOperator::Multiply(_) + | BinaryOperator::Divide(_) + | BinaryOperator::Modulo(_) => { + expected == ValueType::Integer + && self + .left_operand() + .can_yield_type_semantics(ValueType::Integer, scope) + && self + .right_operand() + .can_yield_type_semantics(ValueType::Integer, scope) + } + BinaryOperator::Equal(..) + | BinaryOperator::NotEqual(..) + | BinaryOperator::GreaterThan(_) + | BinaryOperator::GreaterThanOrEqual(..) + | BinaryOperator::LessThan(_) + | BinaryOperator::LessThanOrEqual(..) => expected == ValueType::Boolean, + BinaryOperator::LogicalAnd(..) | BinaryOperator::LogicalOr(..) => { + expected == ValueType::Boolean + && self + .left_operand() + .can_yield_type_semantics(ValueType::Boolean, scope) + && self + .right_operand() + .can_yield_type_semantics(ValueType::Boolean, scope) } } } } -impl FunctionCall { - /// Analyzes the semantics of a function call. - pub fn analyze_semantics( +impl LuaCode { + #[expect(clippy::unused_self, clippy::unnecessary_wraps)] + fn analyze_semantics( &self, - function_names: &HashSet, - macro_names: &HashSet, - handler: &impl Handler, + _scope: &SemanticScope, + _handler: &impl Handler, ) -> Result<(), error::Error> { - let mut errors = Vec::new(); - - // TODO: also check for internal functions - // 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) { + cfg_if::cfg_if! { + if #[cfg(feature = "lua")] { + // TODO: check which type is returned + Ok(()) + } else { + let err = error::Error::LuaDisabled; handler.receive(err.clone()); - errors.push(err); + Err(err) } } + } - #[expect(clippy::option_if_let_else)] - if let Some(err) = errors.first() { - Err(err.clone()) - } else { - Ok(()) - } + #[expect(clippy::unused_self)] + #[must_use] + fn can_yield_type_semantics(&self, _expected: ValueType) -> bool { + // TODO: check which type is returned + cfg!(feature = "lua") } } impl AnyStringLiteral { - /// Analyzes the semantics of any string literal. - pub fn analyze_semantics( + pub(crate) fn analyze_semantics( &self, - macro_names: &HashSet, + scope: &SemanticScope, handler: &impl Handler, ) -> Result<(), error::Error> { match self { Self::StringLiteral(_) => Ok(()), - Self::MacroStringLiteral(literal) => literal.analyze_semantics(macro_names, handler), + Self::MacroStringLiteral(lit) => lit.analyze_semantics(scope, handler), } } } -impl VariableDeclaration { - /// Analyzes the semantics of a variable declaration. - pub fn analyze_semantics( +impl MacroStringLiteral { + fn analyze_semantics( &self, - _function_names: &HashSet, - _macro_names: &HashSet, - _handler: &impl Handler, + scope: &SemanticScope, + handler: &impl Handler, ) -> Result<(), error::Error> { - // match self { - // Self::Array(array) => array.assignment().as_ref().map_or(Ok(()), |assignment| { - // assignment - // .expression() - // .analyze_semantics(function_names, macro_names, handler) - // }), - // Self::Single(single) => { - // if let Some(assignment) = single.assignment() { - // let err = match single.variable_type().keyword { - // KeywordKind::Int => { - // !matches!( - // assignment.expression(), - // // TODO: also allow macro identifier but not macro string literal - // Expression::Primary( - // Primary::Integer(_) - // | Primary::Lua(_) - // | Primary::FunctionCall(_) - // ) - // ) && !matches!(assignment.expression(), Expression::Binary(..)) - // } - // KeywordKind::Bool => !matches!( - // assignment.expression(), - // Expression::Primary( - // Primary::Boolean(_) | Primary::Lua(_) | Primary::FunctionCall(_) - // ) - // ), - // _ => false, - // }; - // if err { - // let err = error::Error::UnexpectedExpression(UnexpectedExpression( - // assignment.expression().clone(), - // )); - // handler.receive(err.clone()); - // return Err(err); - // } - // assignment - // .expression() - // .analyze_semantics(function_names, macro_names, handler) - // } else { - // Ok(()) - // } - // } - // Self::Score(score) => { - // if let Some((_, assignment)) = score.target_assignment() { - // // TODO: also allow macro identifier but not macro string literal - // if !matches!( - // assignment.expression(), - // Expression::Primary( - // Primary::Integer(_) | Primary::Lua(_) | Primary::FunctionCall(_) - // ) - // ) { - // let err = error::Error::UnexpectedExpression(UnexpectedExpression( - // assignment.expression().clone(), - // )); - // handler.receive(err.clone()); - // return Err(err); - // } - // assignment - // .expression() - // .analyze_semantics(function_names, macro_names, handler) - // } else { - // Ok(()) - // } - // } - // Self::Tag(tag) => { - // if let Some((_, assignment)) = tag.target_assignment() { - // if !matches!( - // assignment.expression(), - // Expression::Primary(Primary::Boolean(_) | Primary::Lua(_)) - // ) { - // let err = error::Error::UnexpectedExpression(UnexpectedExpression( - // assignment.expression().clone(), - // )); - // handler.receive(err.clone()); - // return Err(err); - // } - // assignment - // .expression() - // .analyze_semantics(function_names, macro_names, handler) - // } else { - // Ok(()) - // } - // } - // } - // TODO: correctly analyze the semantics of the variable declaration - Ok(()) + let mut errs = Vec::new(); + + for part in self.parts() { + match part { + MacroStringLiteralPart::MacroUsage { identifier, .. } => { + if let Some(variable_type) = scope.get_variable(identifier.span.str()) { + if variable_type != VariableType::MacroParameter { + let err = error::Error::UnexpectedExpression(UnexpectedExpression( + Expression::Primary(Primary::Identifier(identifier.clone())), + )); + handler.receive(err.clone()); + errs.push(err); + } + } else { + let err = error::Error::UnknownIdentifier(UnknownIdentifier { + identifier: identifier.span.clone(), + }); + handler.receive(err.clone()); + errs.push(err); + } + } + MacroStringLiteralPart::Text(_) => {} + } + } + + errs.into_iter().next().map_or(Ok(()), Err) } } diff --git a/src/semantic/scope.rs b/src/semantic/scope.rs new file mode 100644 index 0000000..ce1ce0f --- /dev/null +++ b/src/semantic/scope.rs @@ -0,0 +1,95 @@ +use std::{collections::HashMap, sync::RwLock}; + +/// Type of variable +#[derive(Clone, Debug, Copy, PartialEq, Eq)] +pub enum VariableType { + /// A function. + Function, + /// A macro function parameter. + MacroParameter, + /// A scoreboard. + Scoreboard, + /// A scoreboard value. + ScoreboardValue, + /// Multiple values stored in scoreboard. + ScoreboardArray, + /// A tag applied to entities. + Tag, + /// A boolean stored in a data storage. + BooleanStorage, + /// Multiple booleans stored in a data storage array. + BooleanStorageArray, + /// Compiler internal function. + InternalFunction, +} + +/// A scope that stores variables. +#[derive(Debug, Default)] +pub struct SemanticScope<'a> { + /// Parent scope where variables are inherited from. + parent: Option<&'a Self>, + /// Variables stored in the scope. + variables: RwLock>, +} + +impl<'a> SemanticScope<'a> { + /// Creates a new scope. + #[must_use] + pub fn new() -> Self { + let scope = Self::default(); + + scope.set_variable("print", VariableType::InternalFunction); + + scope + } + + /// Creates a new scope with a parent. + #[must_use] + pub fn with_parent(parent: &'a Self) -> Self { + Self { + parent: Some(parent), + ..Default::default() + } + } + + /// Gets a variable from the scope. + pub fn get_variable(&self, name: &str) -> Option { + let var = self.variables.read().unwrap().get(name).copied(); + if var.is_some() { + var + } else { + self.parent + .as_ref() + .and_then(|parent| parent.get_variable(name)) + } + } + + /// Sets a variable in the scope. + pub fn set_variable(&self, name: &str, var: VariableType) { + self.variables + .write() + .unwrap() + .insert(name.to_string(), var); + } + + /// Gets the variables stored in the current scope. + pub fn get_local_variables(&self) -> &RwLock> { + &self.variables + } + + /// Gets all variables stored in the scope. + /// + /// This function does not return a reference to the variables, but clones them. + pub fn get_all_variables(&self) -> HashMap { + let mut variables = self.variables.read().unwrap().clone(); + if let Some(parent) = self.parent.as_ref() { + variables.extend(parent.get_all_variables()); + } + variables + } + + /// Gets the parent scope. + pub fn get_parent(&self) -> Option<&Self> { + self.parent + } +} diff --git a/src/syntax/syntax_tree/expression.rs b/src/syntax/syntax_tree/expression.rs index 4bb0eb4..eb5bc1f 100644 --- a/src/syntax/syntax_tree/expression.rs +++ b/src/syntax/syntax_tree/expression.rs @@ -247,6 +247,14 @@ impl SourceElement for Parenthesized { } } +impl std::ops::Deref for Parenthesized { + type Target = Expression; + + fn deref(&self) -> &Self::Target { + &self.expression + } +} + /// Represents a indexed expression in the syntax tree. /// /// Syntax Synopsis: diff --git a/src/syntax/syntax_tree/statement/execute_block.rs b/src/syntax/syntax_tree/statement/execute_block.rs index 4bd2133..e085e46 100644 --- a/src/syntax/syntax_tree/statement/execute_block.rs +++ b/src/syntax/syntax_tree/statement/execute_block.rs @@ -1,7 +1,5 @@ //! Execute block statement syntax tree. -use std::collections::HashSet; - use derive_more::From; use enum_as_inner::EnumAsInner; use getset::Getters; @@ -998,10 +996,10 @@ pub trait ExecuteBlockHeadItem { #[expect(clippy::missing_errors_doc)] fn analyze_semantics( &self, - macro_names: &HashSet, + scope: &crate::semantic::SemanticScope, handler: &impl Handler, ) -> Result<(), crate::semantic::error::Error> { - self.selector().analyze_semantics(macro_names, handler) + self.selector().analyze_semantics(scope, handler) } } diff --git a/src/transpile/error.rs b/src/transpile/error.rs index b3a5537..89d2ce5 100644 --- a/src/transpile/error.rs +++ b/src/transpile/error.rs @@ -44,6 +44,8 @@ pub enum TranspileError { MissingValue(#[from] MissingValue), #[error(transparent)] IllegalIndexing(#[from] IllegalIndexing), + #[error(transparent)] + InvalidArgument(#[from] InvalidArgument), } /// The result of a transpilation operation. @@ -52,8 +54,10 @@ pub type TranspileResult = Result; /// An error that occurs when a function declaration is missing. #[derive(Debug, Clone, PartialEq, Eq, Getters)] pub struct MissingFunctionDeclaration { + /// The span of the identifier that is missing. #[get = "pub"] span: Span, + /// Possible alternatives for the missing function declaration. #[get = "pub"] alternatives: Vec, } @@ -127,11 +131,26 @@ impl Display for MissingFunctionDeclaration { impl std::error::Error for MissingFunctionDeclaration {} +impl std::hash::Hash for MissingFunctionDeclaration { + fn hash(&self, state: &mut H) { + self.span.hash(state); + for alternative in &self.alternatives { + alternative.identifier_span.hash(state); + alternative.namespace.hash(state); + alternative.parameters.hash(state); + alternative.public.hash(state); + alternative.statements.hash(state); + } + } +} + /// An error that occurs when a function declaration is missing. #[allow(clippy::module_name_repetitions)] #[derive(Debug, Clone, PartialEq, Eq)] pub struct LuaRuntimeError { + /// The span of the code block that caused the error. pub code_block: Span, + /// The error message of the Lua runtime. pub error_message: String, } @@ -155,6 +174,8 @@ impl std::error::Error for LuaRuntimeError {} #[cfg(feature = "lua")] impl LuaRuntimeError { + /// Creates a new Lua runtime error from an mlua error. + #[must_use] pub fn from_lua_err(err: &mlua::Error, span: Span) -> Self { let err_string = err.to_string(); Self { @@ -168,9 +189,13 @@ impl LuaRuntimeError { } /// An error that occurs when an annotation has an illegal content. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Getters)] pub struct IllegalAnnotationContent { + /// The span of the annotation. + #[get = "pub"] pub annotation: Span, + /// The error message. + #[get = "pub"] pub message: String, } @@ -194,9 +219,13 @@ impl Display for IllegalAnnotationContent { impl std::error::Error for IllegalAnnotationContent {} /// An error that occurs when an expression can not evaluate to the wanted type. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Getters)] pub struct MismatchedTypes { + /// The expression that can not evaluate to the wanted type. + #[get = "pub"] pub expression: Span, + /// The expected type. + #[get = "pub"] pub expected_type: ExpectedType, } @@ -216,9 +245,13 @@ impl Display for MismatchedTypes { impl std::error::Error for MismatchedTypes {} /// An error that occurs when an expression can not evaluate to the wanted type. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Getters)] pub struct FunctionArgumentsNotAllowed { + /// The arguments that are not allowed. + #[get = "pub"] pub arguments: Span, + /// The error message. + #[get = "pub"] pub message: String, } @@ -237,9 +270,13 @@ impl Display for FunctionArgumentsNotAllowed { impl std::error::Error for FunctionArgumentsNotAllowed {} /// An error that occurs when an expression can not evaluate to the wanted type. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Getters)] pub struct AssignmentError { + /// The identifier that is assigned to. + #[get = "pub"] pub identifier: Span, + /// The error message. + #[get = "pub"] pub message: String, } @@ -258,8 +295,10 @@ impl Display for AssignmentError { impl std::error::Error for AssignmentError {} /// An error that occurs when an unknown identifier is used. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Getters)] pub struct UnknownIdentifier { + /// The unknown identifier. + #[get = "pub"] pub identifier: Span, } @@ -285,8 +324,10 @@ impl Display for UnknownIdentifier { impl std::error::Error for UnknownIdentifier {} /// An error that occurs when there is a value expected but none provided. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Getters)] pub struct MissingValue { + /// The expression that is missing a value. + #[get = "pub"] pub expression: Span, } @@ -312,9 +353,13 @@ impl Display for MissingValue { impl std::error::Error for MissingValue {} /// An error that occurs when an indexing operation is not permitted. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Getters)] pub struct IllegalIndexing { + /// The reason why the indexing operation is not permitted. + #[get = "pub"] pub reason: IllegalIndexingReason, + /// The expression that is the reason for the indexing being illegal. + #[get = "pub"] pub expression: Span, } @@ -333,11 +378,24 @@ impl Display for IllegalIndexing { impl std::error::Error for IllegalIndexing {} /// The reason why an indexing operation is not permitted. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum IllegalIndexingReason { + /// The expression is not an identifier. NotIdentifier, - InvalidComptimeType { expected: ExpectedType }, - IndexOutOfBounds { index: usize, length: usize }, + /// The expression cannot be indexed. + NotIndexable, + /// The expression can only be indexed with a specific type that can be evaluated at compile time. + InvalidComptimeType { + /// The expected type. + expected: ExpectedType, + }, + /// The index is out of bounds. + IndexOutOfBounds { + /// The index that is out of bounds. + index: usize, + /// The length indexed object. + length: usize, + }, } impl Display for IllegalIndexingReason { @@ -346,6 +404,9 @@ impl Display for IllegalIndexingReason { Self::NotIdentifier => { write!(f, "The expression is not an identifier.") } + Self::NotIndexable => { + write!(f, "The expression cannot be indexed.") + } Self::InvalidComptimeType { expected } => { write!( f, @@ -361,3 +422,28 @@ impl Display for IllegalIndexingReason { } } } + +/// An error that occurs when an indexing operation is not permitted. +#[derive(Debug, Clone, PartialEq, Eq, Getters)] +pub struct InvalidArgument { + /// The span of the argument. + #[get = "pub"] + pub span: Span, + /// The reason why the argument is invalid. + #[get = "pub"] + pub reason: String, +} + +impl Display for InvalidArgument { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", Message::new(Severity::Error, &self.reason))?; + + write!( + f, + "\n{}", + SourceCodeDisplay::new(&self.span, Option::::None) + ) + } +} + +impl std::error::Error for InvalidArgument {} diff --git a/src/transpile/expression.rs b/src/transpile/expression.rs index 1cbb6f0..cf8cacc 100644 --- a/src/transpile/expression.rs +++ b/src/transpile/expression.rs @@ -85,7 +85,7 @@ impl Display for ValueType { } #[allow(missing_docs)] -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ExpectedType { Boolean, Integer, @@ -915,7 +915,7 @@ impl Transpiler { } } - fn transpile_binary_expression( + pub(super) fn transpile_binary_expression( &mut self, binary: &Binary, target: &DataLocation, diff --git a/src/transpile/function.rs b/src/transpile/function.rs index 6e90a90..fde399a 100644 --- a/src/transpile/function.rs +++ b/src/transpile/function.rs @@ -361,7 +361,18 @@ impl Transpiler { path: std::mem::take(&mut temp_path[0]), }) } - _ => todo!("other variable types"), + _ => { + let err = TranspileError::MismatchedTypes(MismatchedTypes { + expression: expression.span(), + expected_type: ExpectedType::AnyOf(vec![ + ExpectedType::Integer, + ExpectedType::Boolean, + ExpectedType::String, + ]), + }); + handler.receive(err.clone()); + Err(err) + } } } Expression::Primary( diff --git a/src/transpile/internal_functions.rs b/src/transpile/internal_functions.rs index 09dc0db..ebd9a96 100644 --- a/src/transpile/internal_functions.rs +++ b/src/transpile/internal_functions.rs @@ -5,6 +5,7 @@ use std::{ sync::Arc, }; +use cfg_if::cfg_if; use shulkerbox::prelude::{Command, Execute}; use serde_json::{json, Value as JsonValue}; @@ -15,8 +16,8 @@ use crate::{ semantic::error::{InvalidFunctionArguments, UnexpectedExpression}, syntax::syntax_tree::expression::{Expression, FunctionCall, Primary}, transpile::{ - error::{IllegalIndexing, IllegalIndexingReason, LuaRuntimeError, UnknownIdentifier}, - expression::{ComptimeValue, DataLocation, StorageType}, + error::{IllegalIndexing, IllegalIndexingReason, UnknownIdentifier}, + expression::{ComptimeValue, DataLocation, ExpectedType, StorageType}, util::MacroString, TranspileError, }, @@ -210,17 +211,24 @@ fn print_function( Vec::new(), vec![JsonValue::String(string.str_content().to_string())], )), + #[cfg_attr(not(feature = "lua"), expect(unused_variables))] Primary::Lua(lua) => { - let (ret, _lua) = lua.eval(scope, &VoidHandler)?; - Ok(( - Vec::new(), - vec![JsonValue::String(ret.to_string().map_err(|err| { - TranspileError::LuaRuntimeError(LuaRuntimeError::from_lua_err( - &err, - lua.span(), + cfg_if! { + if #[cfg(feature = "lua")] { + let (ret, _lua) = lua.eval(scope, &VoidHandler)?; + Ok(( + Vec::new(), + vec![JsonValue::String(ret.to_string().map_err(|err| { + TranspileError::LuaRuntimeError(super::error::LuaRuntimeError::from_lua_err( + &err, + lua.span(), + )) + })?)], )) - })?)], - )) + } else { + Err(TranspileError::LuaDisabled) + } + } } Primary::Identifier(ident) => { let (cur_contains_macro, cmd, part) = @@ -244,7 +252,13 @@ fn print_function( ); Ok((cmd.into_iter().collect(), vec![value])) } else { - todo!("allow macro string, but throw error when index is not constant string") + // TODO: allow macro string, but throw error when index is not constant string + Err(TranspileError::IllegalIndexing(IllegalIndexing { + reason: IllegalIndexingReason::InvalidComptimeType { + expected: ExpectedType::String, + }, + expression: indexed.index().span(), + })) } } Some(VariableData::ScoreboardArray { objective, targets }) => { @@ -274,7 +288,12 @@ fn print_function( })) } } else { - todo!("throw error when index is not constant integer") + Err(TranspileError::IllegalIndexing(IllegalIndexing { + reason: IllegalIndexingReason::InvalidComptimeType { + expected: ExpectedType::Integer, + }, + expression: indexed.index().span(), + })) } } Some(VariableData::BooleanStorageArray { @@ -308,10 +327,18 @@ fn print_function( })) } } else { - todo!("throw error when index is not constant integer") + Err(TranspileError::IllegalIndexing(IllegalIndexing { + reason: IllegalIndexingReason::InvalidComptimeType { + expected: ExpectedType::Integer, + }, + expression: indexed.index().span(), + })) } } - _ => todo!("catch illegal indexing"), + _ => Err(TranspileError::IllegalIndexing(IllegalIndexing { + reason: IllegalIndexingReason::NotIndexable, + expression: indexed.object().span(), + })), } } _ => Err(TranspileError::IllegalIndexing(IllegalIndexing { @@ -339,9 +366,43 @@ fn print_function( Ok((cmds, parts)) } - _ => todo!("print_function Primary"), + primary => { + let (storage_name, mut storage_paths) = transpiler.get_temp_storage_locations(1); + let location = DataLocation::Storage { + storage_name, + path: std::mem::take(&mut storage_paths[0]), + r#type: StorageType::Int, + }; + let cmds = transpiler.transpile_primary_expression( + primary, + &location, + scope, + &VoidHandler, + )?; + let (cmd, part) = get_data_location(&location, transpiler); + + Ok(( + cmds.into_iter().chain(cmd.into_iter()).collect(), + vec![part], + )) + } }, - Expression::Binary(_) => todo!("print_function Binary"), + Expression::Binary(binary) => { + let (storage_name, mut storage_paths) = transpiler.get_temp_storage_locations(1); + let location = DataLocation::Storage { + storage_name, + path: std::mem::take(&mut storage_paths[0]), + r#type: StorageType::Int, + }; + let cmds = + transpiler.transpile_binary_expression(binary, &location, scope, &VoidHandler)?; + let (cmd, part) = get_data_location(&location, transpiler); + + Ok(( + cmds.into_iter().chain(cmd.into_iter()).collect(), + vec![part], + )) + } }?; // TODO: prepend prefix with datapack name to parts and remove following diff --git a/src/transpile/lua.rs b/src/transpile/lua.rs index 21600c3..db99db7 100644 --- a/src/transpile/lua.rs +++ b/src/transpile/lua.rs @@ -12,7 +12,7 @@ mod enabled { syntax::syntax_tree::expression::LuaCode, transpile::{ error::{ - LuaRuntimeError, MismatchedTypes, TranspileError, TranspileResult, + InvalidArgument, LuaRuntimeError, MismatchedTypes, TranspileError, TranspileResult, UnknownIdentifier, }, expression::{ComptimeValue, ExpectedType}, @@ -263,7 +263,11 @@ mod enabled { Value::Table(table) } Some(VariableData::Function { .. } | VariableData::InternalFunction { .. }) => { - todo!("(internal) functions are not supported yet"); + // TODO: add support for functions + return Err(TranspileError::InvalidArgument(InvalidArgument { + reason: "functions cannot be passed to Lua".to_string(), + span: identifier.span(), + })); } None => { return Err(TranspileError::UnknownIdentifier(UnknownIdentifier { @@ -397,7 +401,7 @@ mod disabled { &self, scope: &Arc, handler: &impl Handler, - ) -> TranspileResult<()> { + ) -> TranspileResult<((), ())> { let _ = scope; handler.receive(TranspileError::LuaDisabled); tracing::error!("Lua code evaluation is disabled"); diff --git a/src/transpile/mod.rs b/src/transpile/mod.rs index 0f72685..e610707 100644 --- a/src/transpile/mod.rs +++ b/src/transpile/mod.rs @@ -16,7 +16,7 @@ use crate::{ #[doc(hidden)] #[cfg(feature = "shulkerbox")] pub mod conversions; -mod error; +pub mod error; pub mod expression; @@ -58,7 +58,7 @@ pub struct FunctionData { /// Possible values for an annotation. #[expect(clippy::module_name_repetitions)] -#[derive(Debug, Clone, PartialEq, Eq, EnumIs)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, EnumIs)] pub enum TranspileAnnotationValue { /// No value. None, diff --git a/src/transpile/variables.rs b/src/transpile/variables.rs index 734c96c..c7d78e3 100644 --- a/src/transpile/variables.rs +++ b/src/transpile/variables.rs @@ -489,7 +489,15 @@ impl Transpiler { target: s, }), Some(ComptimeValue::MacroString(s)) => { - todo!("indexing scoreboard with macro string: {s:?}") + // TODO: allow indexing with macro string + let err = TranspileError::IllegalIndexing(IllegalIndexing { + reason: IllegalIndexingReason::InvalidComptimeType { + expected: ExpectedType::String, + }, + expression: expression.span(), + }); + handler.receive(err.clone()); + return Err(err); } Some(_) => { let err = TranspileError::IllegalIndexing(IllegalIndexing { @@ -621,7 +629,15 @@ impl Transpiler { entity: s, }), Some(ComptimeValue::MacroString(s)) => { - todo!("indexing tag with macro string: {s:?}") + // TODO: allow indexing tag with macro string + let err = TranspileError::IllegalIndexing(IllegalIndexing { + expression: expression.span(), + reason: IllegalIndexingReason::InvalidComptimeType { + expected: ExpectedType::String, + }, + }); + handler.receive(err.clone()); + return Err(err); } Some(_) => { let err = TranspileError::IllegalIndexing(IllegalIndexing { @@ -931,7 +947,15 @@ impl Transpiler { Ok((name, targets)) } TranspileAnnotationValue::Map(map) => { - todo!("allow map deobfuscate annotation for array variables") + // TODO: implement when map deobfuscate annotation is implemented + let error = + TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { + annotation: deobfuscate_annotation.span(), + message: "Deobfuscate annotation value must be a string or none." + .to_string(), + }); + handler.receive(error.clone()); + Err(error) } TranspileAnnotationValue::Expression(_) => { let error =