implement rest of member access transpilation

This commit is contained in:
Moritz Hölting 2025-08-06 15:28:03 +02:00
parent 927b0f52c1
commit 6e27474da4
6 changed files with 436 additions and 13 deletions

View File

@ -338,6 +338,11 @@ impl SourceElement for Tag {
} }
impl Parser<'_> { impl Parser<'_> {
/// Parses a declaration
///
/// # Errors
/// - cannot parse declaration from current position
#[expect(clippy::too_many_lines)]
#[tracing::instrument(level = "trace", skip_all)] #[tracing::instrument(level = "trace", skip_all)]
pub fn parse_declaration( pub fn parse_declaration(
&mut self, &mut self,

View File

@ -94,6 +94,9 @@ impl Namespace {
impl Parser<'_> { impl Parser<'_> {
/// Parses a [`ProgramFile`]. /// Parses a [`ProgramFile`].
///
/// # Errors
/// - cannot parse a program file from current position
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
pub fn parse_program( pub fn parse_program(
&mut self, &mut self,

View File

@ -854,6 +854,9 @@ impl Parser<'_> {
} }
/// Parses a [`Statement`]. /// Parses a [`Statement`].
///
/// # Errors
/// - cannot parse a [`Statement`] from current position
#[tracing::instrument(level = "trace", skip_all)] #[tracing::instrument(level = "trace", skip_all)]
pub fn parse_statement( pub fn parse_statement(
&mut self, &mut self,
@ -919,6 +922,9 @@ impl Parser<'_> {
} }
/// Parses a [`Semicolon`]. /// Parses a [`Semicolon`].
///
/// # Errors
/// - cannot parse [`Semicolon`] from current position
#[tracing::instrument(level = "trace", skip_all)] #[tracing::instrument(level = "trace", skip_all)]
pub fn parse_semicolon( pub fn parse_semicolon(
&mut self, &mut self,
@ -994,6 +1000,10 @@ impl Parser<'_> {
} }
/// Parses a [`VariableDeclaration`]. /// Parses a [`VariableDeclaration`].
///
/// # Errors
/// - cannot parse variable declaration from current position
#[expect(clippy::too_many_lines)]
#[tracing::instrument(level = "trace", skip_all)] #[tracing::instrument(level = "trace", skip_all)]
pub fn parse_variable_declaration( pub fn parse_variable_declaration(
&mut self, &mut self,

View File

@ -17,11 +17,11 @@ use super::{
}, },
Scope, TranspileResult, Transpiler, VariableData, Scope, TranspileResult, Transpiler, VariableData,
}; };
use crate::lexical::token::{Identifier, StringLiteral}; use crate::syntax::syntax_tree::expression::{Indexed, Parenthesized};
#[cfg(feature = "shulkerbox")] #[cfg(feature = "shulkerbox")]
use crate::{ use crate::{
base::{self, source_file::SourceElement, Handler, VoidHandler}, base::{self, source_file::SourceElement, Handler, VoidHandler},
lexical::token::MacroStringLiteralPart, lexical::token::{Identifier, MacroStringLiteralPart, StringLiteral},
syntax::syntax_tree::expression::{ syntax::syntax_tree::expression::{
Binary, BinaryOperator, Expression, MemberAccess, PrefixOperator, Primary, Binary, BinaryOperator, Expression, MemberAccess, PrefixOperator, Primary,
}, },
@ -320,9 +320,7 @@ impl Primary {
false false
} }
} }
Self::MemberAccess(_) => { Self::MemberAccess(member_access) => member_access.can_yield_type(r#type, scope),
todo!()
}
#[cfg_attr(not(feature = "lua"), expect(unused_variables))] #[cfg_attr(not(feature = "lua"), expect(unused_variables))]
Self::Lua(lua) => { Self::Lua(lua) => {
cfg_if::cfg_if! { cfg_if::cfg_if! {
@ -429,7 +427,7 @@ impl Primary {
} }
} }
fn comptime_member_access( pub(super) fn comptime_member_access(
&self, &self,
member_access: &MemberAccess, member_access: &MemberAccess,
scope: &Arc<Scope>, scope: &Arc<Scope>,
@ -438,12 +436,53 @@ impl Primary {
match self { match self {
Self::StringLiteral(s) => s.comptime_member_access(member_access, scope, handler), Self::StringLiteral(s) => s.comptime_member_access(member_access, scope, handler),
Self::Identifier(ident) => ident.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<Scope>,
) -> 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 { impl StringLiteral {
fn comptime_member_access( fn comptime_member_access(
&self, &self,
@ -461,8 +500,20 @@ impl StringLiteral {
}), }),
} }
} }
fn member_access_can_yield_type(
member_access: &MemberAccess,
r#type: ValueType,
_scope: &Arc<Scope>,
) -> bool {
match member_access.member().span.str() {
"length" => r#type == ValueType::Integer,
_ => false,
}
}
} }
#[cfg(feature = "shulkerbox")]
impl Identifier { impl Identifier {
#[expect(clippy::too_many_lines)] #[expect(clippy::too_many_lines)]
fn comptime_member_access( 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<Scope>,
) -> 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<Scope>,
handler: &impl Handler<base::Error>,
) -> Result<ComptimeValue, NotComptime> {
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<Scope>,
) -> 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<Scope>,
handler: &impl Handler<base::Error>,
) -> Result<ComptimeValue, NotComptime> {
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<Scope>,
) -> 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<Scope>) -> bool {
self.parent()
.member_access_can_yield_type(self, r#type, scope)
}
} }
#[cfg(feature = "shulkerbox")] #[cfg(feature = "shulkerbox")]
@ -748,6 +1041,56 @@ impl Binary {
}), }),
} }
} }
fn comptime_member_access(
&self,
member_access: &MemberAccess,
scope: &Arc<Scope>,
handler: &impl Handler<base::Error>,
) -> Result<ComptimeValue, NotComptime> {
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<Scope>,
) -> 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")] #[cfg(feature = "shulkerbox")]
@ -878,7 +1221,13 @@ impl Transpiler {
Primary::Parenthesized(parenthesized) => { Primary::Parenthesized(parenthesized) => {
self.transpile_expression(parenthesized.expression(), target, scope, handler) 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) => Primary::Lua(lua) =>
{ {
#[expect(clippy::option_if_let_else)] #[expect(clippy::option_if_let_else)]
@ -1441,7 +1790,35 @@ impl Transpiler {
Err(err) 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() { Primary::Prefix(prefix) => match prefix.operator() {
PrefixOperator::LogicalNot(_) => { PrefixOperator::LogicalNot(_) => {
let (cmds, cond) = self.transpile_primary_expression_as_condition( let (cmds, cond) = self.transpile_primary_expression_as_condition(

View File

@ -39,6 +39,7 @@ pub enum TranspiledFunctionArguments {
impl Transpiler { impl Transpiler {
/// Gets the function at the given path, or transpiles it if it hasn't been transpiled yet. /// 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. /// 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))] #[tracing::instrument(level = "trace", skip(self, handler))]
pub(super) fn get_or_transpile_function( pub(super) fn get_or_transpile_function(
&mut self, &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( Expression::Primary(
Primary::Parenthesized(_) Primary::Parenthesized(_)
| Primary::Prefix(_) | Primary::Prefix(_)

View File

@ -68,6 +68,7 @@ impl Transpiler {
/// ///
/// # Errors /// # Errors
/// - [`TranspileError::MissingFunctionDeclaration`] If a called function is missing /// - [`TranspileError::MissingFunctionDeclaration`] If a called function is missing
#[expect(clippy::too_many_lines)]
#[tracing::instrument(level = "trace", skip_all)] #[tracing::instrument(level = "trace", skip_all)]
pub fn transpile( pub fn transpile(
mut self, mut self,
@ -622,7 +623,8 @@ impl Transpiler {
Primary::Integer(_) Primary::Integer(_)
| Primary::Boolean(_) | Primary::Boolean(_)
| Primary::Prefix(_) | Primary::Prefix(_)
| Primary::Indexed(_) => { | Primary::Indexed(_)
| Primary::MemberAccess(_) => {
let error = TranspileError::UnexpectedExpression(UnexpectedExpression( let error = TranspileError::UnexpectedExpression(UnexpectedExpression(
Expression::Primary(expression.clone()), Expression::Primary(expression.clone()),
)); ));
@ -653,8 +655,6 @@ impl Transpiler {
} }
}, },
Primary::MemberAccess(_) => todo!(),
Primary::Parenthesized(parenthesized) => match parenthesized.expression().as_ref() { Primary::Parenthesized(parenthesized) => match parenthesized.expression().as_ref() {
Expression::Primary(expression) => { Expression::Primary(expression) => {
self.transpile_run_expression(expression, scope, handler) self.transpile_run_expression(expression, scope, handler)