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<'_> {
/// 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,

View File

@ -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,

View File

@ -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,

View File

@ -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<Scope>,
@ -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<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 {
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<Scope>,
) -> 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<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")]
@ -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")]
@ -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(

View File

@ -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(_)

View File

@ -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)