From 6e27474da407f372f84b62243bf7c09ef22685cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20H=C3=B6lting?= <87192362+moritz-hoelting@users.noreply.github.com> Date: Wed, 6 Aug 2025 15:28:03 +0200 Subject: [PATCH] implement rest of member access transpilation --- src/syntax/syntax_tree/declaration.rs | 5 + src/syntax/syntax_tree/program.rs | 3 + src/syntax/syntax_tree/statement.rs | 10 + src/transpile/expression.rs | 395 +++++++++++++++++++++++++- src/transpile/function.rs | 30 +- src/transpile/transpiler.rs | 6 +- 6 files changed, 436 insertions(+), 13 deletions(-) diff --git a/src/syntax/syntax_tree/declaration.rs b/src/syntax/syntax_tree/declaration.rs index 8c7c16b..0292f84 100644 --- a/src/syntax/syntax_tree/declaration.rs +++ b/src/syntax/syntax_tree/declaration.rs @@ -338,6 +338,11 @@ impl SourceElement for Tag { } impl Parser<'_> { + /// Parses a declaration + /// + /// # Errors + /// - cannot parse declaration from current position + #[expect(clippy::too_many_lines)] #[tracing::instrument(level = "trace", skip_all)] pub fn parse_declaration( &mut self, diff --git a/src/syntax/syntax_tree/program.rs b/src/syntax/syntax_tree/program.rs index 91658ba..4f027ed 100644 --- a/src/syntax/syntax_tree/program.rs +++ b/src/syntax/syntax_tree/program.rs @@ -94,6 +94,9 @@ impl Namespace { impl Parser<'_> { /// Parses a [`ProgramFile`]. + /// + /// # Errors + /// - cannot parse a program file from current position #[tracing::instrument(level = "debug", skip_all)] pub fn parse_program( &mut self, diff --git a/src/syntax/syntax_tree/statement.rs b/src/syntax/syntax_tree/statement.rs index 518be87..821dd32 100644 --- a/src/syntax/syntax_tree/statement.rs +++ b/src/syntax/syntax_tree/statement.rs @@ -854,6 +854,9 @@ impl Parser<'_> { } /// Parses a [`Statement`]. + /// + /// # Errors + /// - cannot parse a [`Statement`] from current position #[tracing::instrument(level = "trace", skip_all)] pub fn parse_statement( &mut self, @@ -919,6 +922,9 @@ impl Parser<'_> { } /// Parses a [`Semicolon`]. + /// + /// # Errors + /// - cannot parse [`Semicolon`] from current position #[tracing::instrument(level = "trace", skip_all)] pub fn parse_semicolon( &mut self, @@ -994,6 +1000,10 @@ impl Parser<'_> { } /// Parses a [`VariableDeclaration`]. + /// + /// # Errors + /// - cannot parse variable declaration from current position + #[expect(clippy::too_many_lines)] #[tracing::instrument(level = "trace", skip_all)] pub fn parse_variable_declaration( &mut self, diff --git a/src/transpile/expression.rs b/src/transpile/expression.rs index f4d7dd9..d9060f6 100644 --- a/src/transpile/expression.rs +++ b/src/transpile/expression.rs @@ -17,11 +17,11 @@ use super::{ }, Scope, TranspileResult, Transpiler, VariableData, }; -use crate::lexical::token::{Identifier, StringLiteral}; +use crate::syntax::syntax_tree::expression::{Indexed, Parenthesized}; #[cfg(feature = "shulkerbox")] use crate::{ base::{self, source_file::SourceElement, Handler, VoidHandler}, - lexical::token::MacroStringLiteralPart, + lexical::token::{Identifier, MacroStringLiteralPart, StringLiteral}, syntax::syntax_tree::expression::{ Binary, BinaryOperator, Expression, MemberAccess, PrefixOperator, Primary, }, @@ -320,9 +320,7 @@ impl Primary { false } } - Self::MemberAccess(_) => { - todo!() - } + Self::MemberAccess(member_access) => member_access.can_yield_type(r#type, scope), #[cfg_attr(not(feature = "lua"), expect(unused_variables))] Self::Lua(lua) => { cfg_if::cfg_if! { @@ -429,7 +427,7 @@ impl Primary { } } - fn comptime_member_access( + pub(super) fn comptime_member_access( &self, member_access: &MemberAccess, scope: &Arc, @@ -438,12 +436,53 @@ impl Primary { match self { Self::StringLiteral(s) => s.comptime_member_access(member_access, scope, handler), Self::Identifier(ident) => ident.comptime_member_access(member_access, scope, handler), + Self::Indexed(idx) => idx.comptime_member_access(member_access, scope, handler), + Self::Parenthesized(paren) => { + paren.comptime_member_access(member_access, scope, handler) + } - _ => todo!(), + Self::Boolean(_) + | Self::FunctionCall(_) + | Self::Integer(_) + | Self::Lua(_) + | Self::MacroStringLiteral(_) + | Self::MemberAccess(_) + | Self::Prefix(_) => Err(NotComptime { + expression: self.span(), + }), + } + } + + fn member_access_can_yield_type( + &self, + member_access: &MemberAccess, + r#type: ValueType, + scope: &Arc, + ) -> bool { + match self { + Self::StringLiteral(_) => { + StringLiteral::member_access_can_yield_type(member_access, r#type, scope) + } + Self::Identifier(ident) => { + ident.member_access_can_yield_type(member_access, r#type, scope) + } + Self::Indexed(idx) => idx.member_access_can_yield_type(member_access, r#type, scope), + Self::Parenthesized(paren) => { + paren.member_access_can_yield_type(member_access, r#type, scope) + } + + Self::Boolean(_) + | Self::FunctionCall(_) + | Self::Integer(_) + | Self::Lua(_) + | Self::MacroStringLiteral(_) + | Self::MemberAccess(_) + | Self::Prefix(_) => false, } } } +#[cfg(feature = "shulkerbox")] impl StringLiteral { fn comptime_member_access( &self, @@ -461,8 +500,20 @@ impl StringLiteral { }), } } + + fn member_access_can_yield_type( + member_access: &MemberAccess, + r#type: ValueType, + _scope: &Arc, + ) -> bool { + match member_access.member().span.str() { + "length" => r#type == ValueType::Integer, + _ => false, + } + } } +#[cfg(feature = "shulkerbox")] impl Identifier { #[expect(clippy::too_many_lines)] fn comptime_member_access( @@ -598,6 +649,248 @@ impl Identifier { }, ) } + + fn member_access_can_yield_type( + &self, + member_access: &MemberAccess, + r#type: ValueType, + scope: &Arc, + ) -> bool { + scope + .get_variable(self.span.str()) + .is_some_and(|data| match data.as_ref() { + VariableData::ComptimeValue { value, .. } => { + let value = value.read().unwrap(); + value.as_ref().is_some_and(|value| match value { + ComptimeValue::String(_) => match member_access.member().span.str() { + "length" => r#type == ValueType::Integer, + _ => false, + }, + _ => false, + }) + } + VariableData::BooleanStorage { .. } => match member_access.member().span.str() { + "path" | "storage" => r#type == ValueType::String, + _ => false, + }, + VariableData::BooleanStorageArray { .. } => { + match member_access.member().span.str() { + "storage" => r#type == ValueType::String, + #[expect(clippy::match_same_arms)] + "paths" => { + // TODO: implement when comptime arrays are implemented + false + } + _ => false, + } + } + VariableData::Function { .. } => match member_access.member().span.str() { + "path" => r#type == ValueType::String, + _ => false, + }, + VariableData::InternalFunction { .. } => false, + VariableData::MacroParameter { .. } | VariableData::Tag { .. } => { + match member_access.member().span.str() { + "name" => r#type == ValueType::String, + _ => false, + } + } + VariableData::Scoreboard { .. } => match member_access.member().span.str() { + "objective" => r#type == ValueType::String, + _ => false, + }, + VariableData::ScoreboardArray { .. } => { + match member_access.member().span.str() { + "objective" => r#type == ValueType::String, + #[expect(clippy::match_same_arms)] + "targets" => { + // TODO: implement when comptime arrays are implemented + false + } + _ => false, + } + } + VariableData::ScoreboardValue { .. } => match member_access.member().span.str() { + "target" | "objective" => r#type == ValueType::String, + _ => false, + }, + }) + } +} + +#[cfg(feature = "shulkerbox")] +impl Indexed { + fn comptime_member_access( + &self, + member_access: &MemberAccess, + scope: &Arc, + handler: &impl Handler, + ) -> Result { + match self.object().as_ref() { + Primary::Identifier(ident) => scope.get_variable(ident.span.str()).map_or_else( + || { + Err(NotComptime { + expression: self.span(), + }) + }, + |data| match data.as_ref() { + VariableData::BooleanStorageArray { + storage_name, + paths, + } => { + if let Ok(ComptimeValue::Integer(idx)) = + self.index().comptime_eval(scope, handler) + { + usize::try_from(idx).map_or_else( + |_| { + Err(NotComptime { + expression: self.index().span(), + }) + }, + |idx| { + paths.get(idx).map_or_else( + || { + Err(NotComptime { + expression: self.span(), + }) + }, + |path| match member_access.member().span.str() { + "storage" => { + Ok(ComptimeValue::String(storage_name.to_owned())) + } + "path" => Ok(ComptimeValue::String(path.to_owned())), + _ => Err(NotComptime { + expression: member_access.member().span(), + }), + }, + ) + }, + ) + } else { + Err(NotComptime { + expression: self.index().span(), + }) + } + } + VariableData::ScoreboardArray { objective, targets } => { + if let Ok(ComptimeValue::Integer(idx)) = + self.index().comptime_eval(scope, handler) + { + usize::try_from(idx).map_or_else( + |_| { + Err(NotComptime { + expression: self.index().span(), + }) + }, + |idx| { + targets.get(idx).map_or_else( + || { + Err(NotComptime { + expression: self.span(), + }) + }, + |target| match member_access.member().span.str() { + "objective" => { + Ok(ComptimeValue::String(objective.to_owned())) + } + "target" => { + Ok(ComptimeValue::String(target.to_owned())) + } + _ => Err(NotComptime { + expression: member_access.member().span(), + }), + }, + ) + }, + ) + } else { + Err(NotComptime { + expression: self.index().span(), + }) + } + } + _ => Err(NotComptime { + expression: self.span(), + }), + }, + ), + _ => Err(NotComptime { + expression: self.span(), + }), + } + } + + fn member_access_can_yield_type( + &self, + member_access: &MemberAccess, + r#type: ValueType, + scope: &Arc, + ) -> bool { + match self.object().as_ref() { + Primary::Identifier(ident) => { + scope + .get_variable(ident.span.str()) + .is_some_and(|data| match data.as_ref() { + VariableData::BooleanStorageArray { .. } => { + matches!(r#type, ValueType::String) + && matches!(member_access.member().span.str(), "storage" | "path") + && self.index().can_yield_type(ValueType::Integer, scope) + } + VariableData::ScoreboardArray { .. } => { + matches!(r#type, ValueType::String) + && matches!( + member_access.member().span.str(), + "objective" | "target" + ) + && self.index().can_yield_type(ValueType::Integer, scope) + } + _ => false, + }) + } + _ => false, + } + } +} + +#[cfg(feature = "shulkerbox")] +impl Parenthesized { + fn comptime_member_access( + &self, + member_access: &MemberAccess, + scope: &Arc, + handler: &impl Handler, + ) -> Result { + match self.expression().as_ref() { + Expression::Primary(prim) => prim.comptime_member_access(member_access, scope, handler), + Expression::Binary(bin) => bin.comptime_member_access(member_access, scope, handler), + } + } + + fn member_access_can_yield_type( + &self, + member_access: &MemberAccess, + r#type: ValueType, + scope: &Arc, + ) -> bool { + match self.expression().as_ref() { + Expression::Primary(prim) => { + prim.member_access_can_yield_type(member_access, r#type, scope) + } + Expression::Binary(bin) => { + bin.member_access_can_yield_type(member_access, r#type, scope) + } + } + } +} + +#[cfg(feature = "shulkerbox")] +impl MemberAccess { + /// Returns whether the member access can yield a certain type. + #[must_use] + pub fn can_yield_type(&self, r#type: ValueType, scope: &Arc) -> bool { + self.parent() + .member_access_can_yield_type(self, r#type, scope) + } } #[cfg(feature = "shulkerbox")] @@ -748,6 +1041,56 @@ impl Binary { }), } } + + fn comptime_member_access( + &self, + member_access: &MemberAccess, + scope: &Arc, + handler: &impl Handler, + ) -> Result { + match self.operator() { + BinaryOperator::Add(_) => match self.comptime_eval(scope, handler)? { + ComptimeValue::String(s) => match member_access.member().span.str() { + "length" => i64::try_from(s.len()).map_or_else( + |_| { + Err(NotComptime { + expression: self.span(), + }) + }, + |len| Ok(ComptimeValue::Integer(len)), + ), + _ => Err(NotComptime { + expression: self.span(), + }), + }, + _ => Err(NotComptime { + expression: self.span(), + }), + }, + _ => Err(NotComptime { + expression: self.span(), + }), + } + } + + fn member_access_can_yield_type( + &self, + member_access: &MemberAccess, + r#type: ValueType, + scope: &Arc, + ) -> bool { + match self.operator() { + BinaryOperator::Add(_) => { + r#type == ValueType::Integer + && member_access.member().span.str() == "length" + && self.left_operand().can_yield_type(ValueType::String, scope) + && self + .right_operand() + .can_yield_type(ValueType::String, scope) + } + _ => false, + } + } } #[cfg(feature = "shulkerbox")] @@ -878,7 +1221,13 @@ impl Transpiler { Primary::Parenthesized(parenthesized) => { self.transpile_expression(parenthesized.expression(), target, scope, handler) } - Primary::MemberAccess(_) => todo!(), + Primary::MemberAccess(member_access) => member_access + .parent() + .comptime_member_access(member_access, scope, handler) + .map_or_else( + |_| todo!("implement non-comptime member access"), + |value| self.store_comptime_value(&value, target, member_access, handler), + ), Primary::Lua(lua) => { #[expect(clippy::option_if_let_else)] @@ -1441,7 +1790,35 @@ impl Transpiler { Err(err) } } - Primary::MemberAccess(_) => todo!(), + Primary::MemberAccess(member_access) => member_access + .parent() + .comptime_member_access(member_access, scope, handler) + .map_or_else( + |_| todo!("implement non-comptime member access"), + |value| match value { + ComptimeValue::Boolean(b) => { + Ok((Vec::new(), ExtendedCondition::Comptime(b))) + } + ComptimeValue::Integer(_) => { + let err = TranspileError::MismatchedTypes(MismatchedTypes { + expected_type: ExpectedType::Boolean, + expression: primary.span(), + }); + handler.receive(err.clone()); + Err(err) + } + ComptimeValue::String(s) => Ok(( + Vec::new(), + ExtendedCondition::Runtime(Condition::Atom( + MacroString::String(s).into(), + )), + )), + ComptimeValue::MacroString(s) => Ok(( + Vec::new(), + ExtendedCondition::Runtime(Condition::Atom(s.into())), + )), + }, + ), Primary::Prefix(prefix) => match prefix.operator() { PrefixOperator::LogicalNot(_) => { let (cmds, cond) = self.transpile_primary_expression_as_condition( diff --git a/src/transpile/function.rs b/src/transpile/function.rs index 4bc7e76..145eb93 100644 --- a/src/transpile/function.rs +++ b/src/transpile/function.rs @@ -39,6 +39,7 @@ pub enum TranspiledFunctionArguments { impl Transpiler { /// Gets the function at the given path, or transpiles it if it hasn't been transpiled yet. /// Returns the location of the function or None if the function does not exist. + #[expect(clippy::too_many_lines)] #[tracing::instrument(level = "trace", skip(self, handler))] pub(super) fn get_or_transpile_function( &mut self, @@ -357,7 +358,34 @@ impl Transpiler { } } } - Expression::Primary(Primary::MemberAccess(_)) => todo!(), + Expression::Primary(Primary::MemberAccess(member_access)) => { + if let Ok(value) = member_access.parent().comptime_member_access( + member_access, + scope, + handler, + ) { + Ok(Parameter::Static(value.to_macro_string())) + } else { + let (storage_name, [path]) = + self.get_temp_storage_locations_array(); + let prepare_cmds = self.transpile_expression( + expression, + &super::expression::DataLocation::Storage { + storage_name: storage_name.clone(), + path: path.clone(), + r#type: StorageType::Int, + }, + scope, + handler, + )?; + + Ok(Parameter::Storage { + prepare_cmds, + storage_name, + path, + }) + } + } Expression::Primary( Primary::Parenthesized(_) | Primary::Prefix(_) diff --git a/src/transpile/transpiler.rs b/src/transpile/transpiler.rs index b5044a9..04cd7f8 100644 --- a/src/transpile/transpiler.rs +++ b/src/transpile/transpiler.rs @@ -68,6 +68,7 @@ impl Transpiler { /// /// # Errors /// - [`TranspileError::MissingFunctionDeclaration`] If a called function is missing + #[expect(clippy::too_many_lines)] #[tracing::instrument(level = "trace", skip_all)] pub fn transpile( mut self, @@ -622,7 +623,8 @@ impl Transpiler { Primary::Integer(_) | Primary::Boolean(_) | Primary::Prefix(_) - | Primary::Indexed(_) => { + | Primary::Indexed(_) + | Primary::MemberAccess(_) => { let error = TranspileError::UnexpectedExpression(UnexpectedExpression( Expression::Primary(expression.clone()), )); @@ -653,8 +655,6 @@ impl Transpiler { } }, - Primary::MemberAccess(_) => todo!(), - Primary::Parenthesized(parenthesized) => match parenthesized.expression().as_ref() { Expression::Primary(expression) => { self.transpile_run_expression(expression, scope, handler)