implement first parts of member access

This commit is contained in:
Moritz Hölting 2025-06-18 11:44:26 +02:00
parent dd97937feb
commit 927b0f52c1
7 changed files with 264 additions and 6 deletions

View File

@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Integer and boolean arrays (scoreboard and data storage)
- Integer map (scoreboard)
- Boolean map (tag)
- Member access (e.g. `.objective` to get objective name where int is stored)
- Return statement
- Example: barebones compiler
### Changed

View File

@ -26,7 +26,7 @@ zip = ["shulkerbox?/zip"]
cfg-if = "1.0.0"
chksum-md5 = { version = "0.1.0", optional = true }
colored = "3.0.0"
derive_more = { version = "2.0.1", default-features = false, features = ["deref", "deref_mut", "from"] }
derive_more = { version = "2.0.1", default-features = false, features = [ "deref", "deref_mut", "from"] }
enum-as-inner = "0.6.0"
getset = "0.1.2"
itertools = "0.14.0"
@ -35,7 +35,7 @@ pathdiff = "0.2.3"
serde = { version = "1.0.217", features = ["derive"], optional = true }
serde_json = { version = "1.0.138", optional = true }
# shulkerbox = { version = "0.1.0", default-features = false, optional = true }
shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", rev = "5c4d340162fc16065add448ed387a1ce481c27d6", default-features = false, optional = true }
shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", rev = "6ff544131b2518b8c92bc4d2de7682efd7141ec4", default-features = false, optional = true }
strsim = "0.11.1"
strum = { version = "0.27.0", features = ["derive"] }
thiserror = "2.0.11"

View File

@ -623,6 +623,10 @@ impl Primary {
Err(err)
}
}
Self::MemberAccess(_) => {
// TODO:
Ok(())
}
Self::Parenthesized(expr) => expr.analyze_semantics(scope, handler),
Self::Prefix(prefixed) => match prefixed.operator() {
PrefixOperator::LogicalNot(_) => {
@ -680,7 +684,9 @@ impl Primary {
match self {
Self::Boolean(_) => expected == ValueType::Boolean,
Self::Integer(_) => expected == ValueType::Integer,
Self::StringLiteral(_) | Self::MacroStringLiteral(_) => expected == ValueType::String,
Self::StringLiteral(_) | Self::MacroStringLiteral(_) => {
matches!(expected, ValueType::String | ValueType::Boolean)
}
Self::FunctionCall(_) => matches!(expected, ValueType::Boolean | ValueType::Integer),
Self::Indexed(indexed) => match indexed.object().as_ref() {
Self::Identifier(ident) => {
@ -699,6 +705,10 @@ impl Primary {
}
_ => false,
},
Self::MemberAccess(_) => {
// TODO:
true
}
Self::Identifier(ident) => match scope.get_variable(ident.span.str()) {
Some(VariableType::BooleanStorage) => expected == ValueType::Boolean,
Some(VariableType::ScoreboardValue) => expected == ValueType::Integer,

View File

@ -175,6 +175,7 @@ impl SourceElement for Expression {
/// | Boolean
/// | StringLiteral
/// | FunctionCall
/// | MemberAccess
/// | MacroStringLiteral
/// | LuaCode
/// ```
@ -190,6 +191,7 @@ pub enum Primary {
Boolean(Boolean),
StringLiteral(StringLiteral),
FunctionCall(FunctionCall),
MemberAccess(MemberAccess),
MacroStringLiteral(MacroStringLiteral),
Lua(Box<LuaCode>),
}
@ -205,6 +207,7 @@ impl SourceElement for Primary {
Self::Boolean(bool) => bool.span(),
Self::StringLiteral(string_literal) => string_literal.span(),
Self::FunctionCall(function_call) => function_call.span(),
Self::MemberAccess(member_access) => member_access.span(),
Self::MacroStringLiteral(macro_string_literal) => macro_string_literal.span(),
Self::Lua(lua_code) => lua_code.span(),
}
@ -443,6 +446,45 @@ impl LuaCode {
}
}
/// Represents a member access in the syntax tree.
///
/// Syntax Synopsis:
///
/// ```ebnf
/// MemberAccess:
/// Primary '.' Identifier
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct MemberAccess {
/// The parent expression
#[get = "pub"]
parent: Box<Primary>,
/// The dot in the middle
#[get = "pub"]
dot: Punctuation,
/// The member being accessed
#[get = "pub"]
member: Identifier,
}
impl SourceElement for MemberAccess {
fn span(&self) -> Span {
self.parent
.span()
.join(&self.member.span)
.expect("invalid span")
}
}
impl MemberAccess {
/// Dissolves the [`MemberAccess`] into its components.
#[must_use]
pub fn dissolve(self) -> (Box<Primary>, Punctuation, Identifier) {
(self.parent, self.dot, self.member)
}
}
impl Parser<'_> {
/// Parses an [`Expression`]
///
@ -509,7 +551,7 @@ impl Parser<'_> {
/// - If the parser is not at a valid primary expression.
#[expect(clippy::too_many_lines)]
pub fn parse_primary(&mut self, handler: &impl Handler<base::Error>) -> ParseResult<Primary> {
match self.stop_at_significant() {
let prim = match self.stop_at_significant() {
// prefixed expression with '!' or '-'
Reading::Atomic(Token::Punctuation(punc)) if matches!(punc.punctuation, '!' | '-') => {
// eat the prefix
@ -702,6 +744,21 @@ impl Parser<'_> {
Err(err)
}
}?;
match self.stop_at_significant() {
Reading::Atomic(Token::Punctuation(punc)) if punc.punctuation == '.' => {
self.forward();
let member = self.parse_identifier(handler)?;
Ok(Primary::MemberAccess(MemberAccess {
parent: Box::new(prim),
dot: punc,
member,
}))
}
_ => Ok(prim),
}
}

View File

@ -17,12 +17,13 @@ use super::{
},
Scope, TranspileResult, Transpiler, VariableData,
};
use crate::lexical::token::{Identifier, StringLiteral};
#[cfg(feature = "shulkerbox")]
use crate::{
base::{self, source_file::SourceElement, Handler, VoidHandler},
lexical::token::MacroStringLiteralPart,
syntax::syntax_tree::expression::{
Binary, BinaryOperator, Expression, PrefixOperator, Primary,
Binary, BinaryOperator, Expression, MemberAccess, PrefixOperator, Primary,
},
transpile::{
error::{FunctionArgumentsNotAllowed, MissingValue},
@ -319,6 +320,9 @@ impl Primary {
false
}
}
Self::MemberAccess(_) => {
todo!()
}
#[cfg_attr(not(feature = "lua"), expect(unused_variables))]
Self::Lua(lua) => {
cfg_if::cfg_if! {
@ -379,6 +383,11 @@ impl Primary {
Self::Parenthesized(parenthesized) => {
parenthesized.expression().comptime_eval(scope, handler)
}
Self::MemberAccess(member_access) => {
member_access
.parent()
.comptime_member_access(member_access, scope, handler)
}
Self::Prefix(prefix) => {
prefix
.operand()
@ -419,6 +428,176 @@ impl Primary {
}
}
}
fn comptime_member_access(
&self,
member_access: &MemberAccess,
scope: &Arc<Scope>,
handler: &impl Handler<base::Error>,
) -> Result<ComptimeValue, NotComptime> {
match self {
Self::StringLiteral(s) => s.comptime_member_access(member_access, scope, handler),
Self::Identifier(ident) => ident.comptime_member_access(member_access, scope, handler),
_ => todo!(),
}
}
}
impl StringLiteral {
fn comptime_member_access(
&self,
member_access: &MemberAccess,
_scope: &Arc<Scope>,
_handler: &impl Handler<base::Error>,
) -> Result<ComptimeValue, NotComptime> {
match member_access.member().span.str() {
"length" => Ok(ComptimeValue::Integer(
i64::try_from(self.str_content().len())
.expect("string literal length should fit in i64"),
)),
_ => Err(NotComptime {
expression: member_access.member().span(),
}),
}
}
}
impl Identifier {
#[expect(clippy::too_many_lines)]
fn comptime_member_access(
&self,
member_access: &MemberAccess,
scope: &Arc<Scope>,
_handler: &impl Handler<base::Error>,
) -> Result<ComptimeValue, NotComptime> {
scope.get_variable(self.span.str()).map_or_else(
|| {
Err(NotComptime {
expression: self.span(),
})
},
|data| match data.as_ref() {
VariableData::ComptimeValue { value, .. } => {
let value = value.read().unwrap();
value.as_ref().map_or_else(
|| {
Err(NotComptime {
expression: self.span(),
})
},
|value| match value {
ComptimeValue::String(s) => match member_access.member().span.str() {
"length" => Ok(ComptimeValue::Integer(
i64::try_from(s.len())
.expect("comptime string length should fit in i64"),
)),
_ => Err(NotComptime {
expression: member_access.member().span(),
}),
},
_ => Err(NotComptime {
expression: self.span(),
}),
},
)
}
VariableData::BooleanStorage { storage_name, 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(),
}),
}
}
VariableData::BooleanStorageArray {
storage_name,
paths: _,
} => {
match member_access.member().span.str() {
"storage" => Ok(ComptimeValue::String(storage_name.to_owned())),
"paths" => {
// TODO: implement when comptime arrays are implemented
Err(NotComptime {
expression: member_access.member().span(),
})
}
_ => Err(NotComptime {
expression: member_access.member().span(),
}),
}
}
VariableData::Function { path, .. } => {
match member_access.member().span.str() {
"path" => {
#[expect(clippy::option_if_let_else)]
if let Some(path) = path.get() {
Ok(ComptimeValue::String(path.to_owned()))
} else {
// TODO: add support for non already compiled functions
Err(NotComptime {
expression: member_access.member().span(),
})
}
}
_ => Err(NotComptime {
expression: member_access.member().span(),
}),
}
}
VariableData::InternalFunction { .. } => Err(NotComptime {
expression: member_access.member().span(),
}),
VariableData::MacroParameter { macro_name, .. } => {
match member_access.member().span.str() {
"name" => Ok(ComptimeValue::String(macro_name.to_owned())),
_ => Err(NotComptime {
expression: member_access.member().span(),
}),
}
}
VariableData::Scoreboard { objective } => match member_access.member().span.str() {
"objective" => Ok(ComptimeValue::String(objective.to_owned())),
_ => Err(NotComptime {
expression: member_access.member().span(),
}),
},
VariableData::ScoreboardArray {
objective,
targets: _,
} => {
match member_access.member().span.str() {
"objective" => Ok(ComptimeValue::String(objective.to_owned())),
"targets" => {
// TODO: implement when comptime arrays are implemented
Err(NotComptime {
expression: member_access.member().span(),
})
}
_ => Err(NotComptime {
expression: member_access.member().span(),
}),
}
}
VariableData::ScoreboardValue { objective, 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(),
}),
}
}
VariableData::Tag { tag_name } => match member_access.member().span.str() {
"name" => Ok(ComptimeValue::String(tag_name.to_owned())),
_ => Err(NotComptime {
expression: member_access.member().span(),
}),
},
},
)
}
}
#[cfg(feature = "shulkerbox")]
@ -699,6 +878,7 @@ impl Transpiler {
Primary::Parenthesized(parenthesized) => {
self.transpile_expression(parenthesized.expression(), target, scope, handler)
}
Primary::MemberAccess(_) => todo!(),
Primary::Lua(lua) =>
{
#[expect(clippy::option_if_let_else)]
@ -1261,6 +1441,7 @@ impl Transpiler {
Err(err)
}
}
Primary::MemberAccess(_) => todo!(),
Primary::Prefix(prefix) => match prefix.operator() {
PrefixOperator::LogicalNot(_) => {
let (cmds, cond) = self.transpile_primary_expression_as_condition(

View File

@ -357,6 +357,7 @@ impl Transpiler {
}
}
}
Expression::Primary(Primary::MemberAccess(_)) => todo!(),
Expression::Primary(
Primary::Parenthesized(_)
| Primary::Prefix(_)

View File

@ -653,6 +653,8 @@ impl Transpiler {
}
},
Primary::MemberAccess(_) => todo!(),
Primary::Parenthesized(parenthesized) => match parenthesized.expression().as_ref() {
Expression::Primary(expression) => {
self.transpile_run_expression(expression, scope, handler)
@ -660,7 +662,7 @@ impl Transpiler {
Expression::Binary(bin) => match bin.comptime_eval(scope, handler) {
Ok(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]),
Ok(ComptimeValue::MacroString(cmd)) => Ok(vec![Command::UsesMacro(cmd.into())]),
_ => {
Ok(_) => {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expression: bin.span(),
expected_type: ExpectedType::String,
@ -668,6 +670,11 @@ impl Transpiler {
handler.receive(err.clone());
Err(err)
}
Err(not_comptime) => {
let err = TranspileError::NotComptime(not_comptime);
handler.receive(err.clone());
Err(err)
}
},
},
}