From 0a8bf37e40eda44a5d8acf57db6c0857072ff0a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20H=C3=B6lting?= <87192362+moritz-hoelting@users.noreply.github.com> Date: Tue, 1 Apr 2025 15:19:22 +0200 Subject: [PATCH] implement global variables (without imports) --- src/semantic/mod.rs | 51 ++++++++++++--- src/syntax/syntax_tree/declaration.rs | 34 ++++++++-- src/transpile/expression.rs | 52 ++++++++++----- src/transpile/lua.rs | 5 +- src/transpile/transpiler.rs | 72 +++++++++++++-------- src/transpile/variables.rs | 92 +++++++++++++++++++++------ 6 files changed, 228 insertions(+), 78 deletions(-) diff --git a/src/semantic/mod.rs b/src/semantic/mod.rs index 3642408..1d18ed7 100644 --- a/src/semantic/mod.rs +++ b/src/semantic/mod.rs @@ -99,6 +99,25 @@ impl Declaration { } } }, + Self::GlobalVariable((var, _)) => { + let name = var.identifier(); + let var_type = match var { + VariableDeclaration::Array(arr) => match arr.variable_type().keyword { + KeywordKind::Bool => VariableType::BooleanStorageArray, + KeywordKind::Int => VariableType::ScoreboardArray, + _ => unreachable!("variable type is not a valid type"), + }, + VariableDeclaration::Score(_) => VariableType::Scoreboard, + VariableDeclaration::Tag(_) => VariableType::Tag, + VariableDeclaration::Single(single) => match single.variable_type().keyword { + KeywordKind::Bool => VariableType::BooleanStorage, + KeywordKind::Int => VariableType::ScoreboardValue, + _ => unreachable!("variable type is not a valid type"), + }, + VariableDeclaration::ComptimeValue(_) => VariableType::ComptimeValue, + }; + scope.set_variable(name.span.str(), var_type); + } Self::Tag(_) => {} } } @@ -137,6 +156,8 @@ impl Declaration { } }, + Self::GlobalVariable((var, _)) => var.analyze_semantics(scope, handler), + Self::Tag(_) => Ok(()), } } @@ -549,16 +570,26 @@ impl Primary { 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) - } + let var = scope.get_variable(call.identifier().span.str()); + var.map_or_else( + || { + let err = error::Error::UnknownIdentifier(UnknownIdentifier { + identifier: call.identifier().span.clone(), + }); + handler.receive(err.clone()); + Err(err) + }, + |var| match var { + VariableType::Function | VariableType::InternalFunction => Ok(()), + _ => { + let err = error::Error::UnexpectedExpression(UnexpectedExpression( + Expression::Primary(self.clone()), + )); + handler.receive(err.clone()); + Err(err) + } + }, + ) } Self::Indexed(indexed) => { if let Self::Identifier(ident) = indexed.object().as_ref() { diff --git a/src/syntax/syntax_tree/declaration.rs b/src/syntax/syntax_tree/declaration.rs index 6c7df5f..bfb8865 100644 --- a/src/syntax/syntax_tree/declaration.rs +++ b/src/syntax/syntax_tree/declaration.rs @@ -23,7 +23,10 @@ use crate::{ }, }; -use super::{statement::Block, Annotation, ConnectedList, DelimitedList}; +use super::{ + statement::{Block, VariableDeclaration}, + Annotation, ConnectedList, DelimitedList, +}; /// Represents a declaration in the syntax tree. /// @@ -31,8 +34,10 @@ use super::{statement::Block, Annotation, ConnectedList, DelimitedList}; /// /// ``` ebnf /// Declaration: -/// Function -/// | Import +/// Function +/// | Import +/// | Tag +/// | (VariableDeclaration ';') /// ; /// ``` #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -41,6 +46,7 @@ pub enum Declaration { Function(Function), Import(Import), Tag(Tag), + GlobalVariable((VariableDeclaration, Punctuation)), } impl SourceElement for Declaration { @@ -49,6 +55,10 @@ impl SourceElement for Declaration { Self::Function(function) => function.span(), Self::Import(import) => import.span(), Self::Tag(tag) => tag.span(), + Self::GlobalVariable((variable, semicolon)) => variable + .span() + .join(&semicolon.span) + .expect("invalid declaration span"), } } } @@ -59,13 +69,17 @@ impl Declaration { /// # Errors /// - if the annotation is invalid for the target declaration. pub fn with_annotation(self, annotation: Annotation) -> ParseResult { - #[expect(clippy::single_match_else)] match self { Self::Function(mut function) => { function.annotations.push_front(annotation); Ok(Self::Function(function)) } + Self::GlobalVariable((var, semi)) => { + let var_with_annotation = var.with_annotation(annotation)?; + + Ok(Self::GlobalVariable((var_with_annotation, semi))) + } _ => { let err = Error::InvalidAnnotation(InvalidAnnotation { annotation: annotation.assignment.identifier.span, @@ -457,6 +471,18 @@ impl Parser<'_> { })) } + Reading::Atomic(Token::Keyword(keyword)) + if matches!( + keyword.keyword, + KeywordKind::Int | KeywordKind::Bool | KeywordKind::Val + ) => + { + let var = self.parse_variable_declaration(handler)?; + let semi = self.parse_punctuation(';', true, handler)?; + + Ok(Declaration::GlobalVariable((var, semi))) + } + unexpected => { // make progress self.forward(); diff --git a/src/transpile/expression.rs b/src/transpile/expression.rs index a0a0cf6..9b8fd5a 100644 --- a/src/transpile/expression.rs +++ b/src/transpile/expression.rs @@ -358,11 +358,12 @@ impl Primary { }) }, |var| match var.as_ref() { - VariableData::ComptimeValue { value } => { - value.read().unwrap().clone().ok_or_else(|| NotComptime { - expression: ident.span.clone(), - }) - } + VariableData::ComptimeValue { + value, + read_only: _, + } => value.read().unwrap().clone().ok_or_else(|| NotComptime { + expression: ident.span.clone(), + }), _ => Err(NotComptime { expression: self.span(), }), @@ -1276,6 +1277,7 @@ impl Transpiler { } } + #[expect(clippy::too_many_lines)] fn transpile_scoreboard_operation( &mut self, binary: &Binary, @@ -1303,20 +1305,38 @@ impl Transpiler { scope, handler, )?; - let right_cmds = self.transpile_expression( - right, - &DataLocation::ScoreboardValue { - objective: temp_objective.clone(), - target: temp_locations[1].clone(), - }, - scope, - handler, - )?; + + let (right_cmds, rhs_score) = + if let Ok(ComptimeValue::Integer(int)) = right.comptime_eval(scope, handler) { + self.initialize_constant_score(int); + ( + Vec::new(), + ("shu_constants", std::borrow::Cow::Owned(int.to_string())), + ) + } else { + let right_cmds = self.transpile_expression( + right, + &DataLocation::ScoreboardValue { + objective: temp_objective.clone(), + target: temp_locations[1].clone(), + }, + scope, + handler, + )?; + + ( + right_cmds, + ( + temp_objective.as_str(), + std::borrow::Cow::Borrowed(&temp_locations[1]), + ), + ) + }; let calc_cmds = { let (target_objective, target) = score_target_location; - let source = &temp_locations[1]; - let source_objective = &temp_objective; + let source = rhs_score.1.as_ref(); + let source_objective = rhs_score.0; let operation = match operator { BinaryOperator::Add(_) => "+=", diff --git a/src/transpile/lua.rs b/src/transpile/lua.rs index 27fa353..c80ce3a 100644 --- a/src/transpile/lua.rs +++ b/src/transpile/lua.rs @@ -266,7 +266,10 @@ mod enabled { .map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?; Value::Table(table) } - Some(VariableData::ComptimeValue { value }) => { + Some(VariableData::ComptimeValue { + value, + read_only: _, + }) => { let value = value.read().unwrap(); match &*value { Some(ComptimeValue::Boolean(b)) => Value::Boolean(*b), diff --git a/src/transpile/transpiler.rs b/src/transpile/transpiler.rs index edac539..44f4322 100644 --- a/src/transpile/transpiler.rs +++ b/src/transpile/transpiler.rs @@ -87,7 +87,7 @@ impl Transpiler { .entry(program_identifier) .or_insert_with(Scope::with_internal_functions) .to_owned(); - self.transpile_program_declarations(program, &scope, handler); + self.transpile_program_declarations(program, &scope, handler)?; } let mut always_transpile_functions = Vec::new(); @@ -143,12 +143,14 @@ impl Transpiler { program: &ProgramFile, scope: &Arc, handler: &impl Handler, - ) { + ) -> TranspileResult<()> { let namespace = program.namespace(); for declaration in program.declarations() { - self.transpile_declaration(declaration, namespace, scope, handler); + self.transpile_declaration(declaration, namespace, scope, handler)?; } + + Ok(()) } /// Transpiles the given declaration. @@ -159,7 +161,7 @@ impl Transpiler { namespace: &Namespace, scope: &Arc, handler: &impl Handler, - ) { + ) -> TranspileResult<()> { let program_identifier = declaration.span().source_file().identifier().clone(); match declaration { Declaration::Function(function) => { @@ -241,9 +243,21 @@ impl Transpiler { if tag.replace().is_some() { sb_tag.set_replace(true); } - // TODO: handle global variables + } + Declaration::GlobalVariable((declaration, _)) => { + let setup_variable_cmds = self.transpile_variable_declaration( + declaration, + true, + &program_identifier, + scope, + handler, + )?; + + self.setup_cmds.extend(setup_variable_cmds); } }; + + Ok(()) } pub(super) fn transpile_statement( @@ -317,9 +331,14 @@ impl Transpiler { Err(error) } }, - SemicolonStatement::VariableDeclaration(decl) => { - self.transpile_variable_declaration(decl, program_identifier, scope, handler) - } + SemicolonStatement::VariableDeclaration(decl) => self + .transpile_variable_declaration( + decl, + false, + program_identifier, + scope, + handler, + ), SemicolonStatement::Assignment(assignment) => self.transpile_assignment( TranspileAssignmentTarget::from(assignment.destination()), assignment.expression(), @@ -344,24 +363,25 @@ impl Transpiler { } Expression::Primary(Primary::Identifier(ident)) => { match scope.get_variable(ident.span.str()).as_deref() { - Some(VariableData::ComptimeValue { value }) => { - value.read().unwrap().as_ref().map_or_else( - || { - let error = TranspileError::MissingValue(MissingValue { - expression: ident.span.clone(), - }); - handler.receive(error.clone()); - Err(error) - }, - |val| { - let cmd = val.to_string_no_macro().map_or_else( - || Command::UsesMacro(val.to_macro_string().into()), - Command::Raw, - ); - Ok(vec![cmd]) - }, - ) - } + Some(VariableData::ComptimeValue { + value, + read_only: _, + }) => value.read().unwrap().as_ref().map_or_else( + || { + let error = TranspileError::MissingValue(MissingValue { + expression: ident.span.clone(), + }); + handler.receive(error.clone()); + Err(error) + }, + |val| { + let cmd = val.to_string_no_macro().map_or_else( + || Command::UsesMacro(val.to_macro_string().into()), + Command::Raw, + ); + Ok(vec![cmd]) + }, + ), Some(_) => { let error = TranspileError::UnexpectedExpression(UnexpectedExpression( expression.clone(), diff --git a/src/transpile/variables.rs b/src/transpile/variables.rs index a256a74..047a814 100644 --- a/src/transpile/variables.rs +++ b/src/transpile/variables.rs @@ -104,6 +104,8 @@ pub enum VariableData { ComptimeValue { /// The value. value: Arc>>, + /// Whether the value is read-only. + read_only: bool, }, } @@ -258,6 +260,7 @@ impl Transpiler { pub(super) fn transpile_variable_declaration( &mut self, declaration: &VariableDeclaration, + is_global: bool, program_identifier: &str, scope: &Arc, handler: &impl Handler, @@ -265,6 +268,7 @@ impl Transpiler { match declaration { VariableDeclaration::Single(declaration) => self.transpile_single_variable_declaration( declaration, + is_global, program_identifier, scope, handler, @@ -277,6 +281,7 @@ impl Transpiler { ), VariableDeclaration::Array(declaration) => self.transpile_array_variable_declaration( declaration, + is_global, program_identifier, scope, handler, @@ -307,6 +312,7 @@ impl Transpiler { declaration.identifier().span.str(), VariableData::ComptimeValue { value: Arc::new(RwLock::new(value)), + read_only: is_global, }, ); @@ -318,6 +324,7 @@ impl Transpiler { fn transpile_single_variable_declaration( &mut self, declaration: &SingleVariableDeclaration, + is_global: bool, program_identifier: &str, scope: &Arc, handler: &impl Handler, @@ -342,7 +349,7 @@ impl Transpiler { declaration.identifier().span.str(), VariableData::ScoreboardValue { objective: name.clone(), - target, + target: target.clone(), }, ); } @@ -350,25 +357,49 @@ impl Transpiler { scope.set_variable( declaration.identifier().span.str(), VariableData::BooleanStorage { - storage_name: name, - path: target, + storage_name: name.clone(), + path: target.clone(), }, ); } _ => unreachable!("no other variable types"), } - declaration.assignment().as_ref().map_or_else( - || Ok(Vec::new()), - |assignment| { - self.transpile_assignment( - TranspileAssignmentTarget::Identifier(declaration.identifier()), - assignment.expression(), - scope, - handler, - ) - }, - ) + if let Some(assignment) = declaration.assignment().as_ref() { + let cmds = self.transpile_assignment( + TranspileAssignmentTarget::Identifier(declaration.identifier()), + assignment.expression(), + scope, + handler, + )?; + if is_global { + let (temp_objective, temp_targets) = self.get_temp_scoreboard_locations(1); + let temp_target = &temp_targets[0]; + let test_cmd = match declaration.variable_type().keyword { + KeywordKind::Int => { + Command::Raw(format!("scoreboard players get {name} {target}")) + } + KeywordKind::Bool => Command::Raw(format!("data get storage {name} {target}")), + _ => unreachable!("no other variable types"), + }; + let test_exists_cmd = Command::Execute(Execute::Store( + format!("success score {temp_target} {temp_objective}").into(), + Box::new(Execute::Run(Box::new(test_cmd))), + )); + let cond_cmd = Command::Execute(Execute::If( + Condition::Atom( + format!("score {temp_target} {temp_objective} matches 0").into(), + ), + Box::new(Execute::Run(Box::new(Command::Group(cmds)))), + None, + )); + Ok(vec![test_exists_cmd, cond_cmd]) + } else { + Ok(cmds) + } + } else { + Ok(Vec::new()) + } } fn transpile_score_variable_declaration( @@ -411,6 +442,7 @@ impl Transpiler { fn transpile_array_variable_declaration( &mut self, declaration: &ArrayVariableDeclaration, + _is_global: bool, program_identifier: &str, scope: &Arc, handler: &impl Handler, @@ -450,6 +482,7 @@ impl Transpiler { declaration.assignment().as_ref().map_or_else( || Ok(Vec::new()), |assignment| { + // TODO: implement global already exists check when array assignments are implemented self.transpile_assignment( TranspileAssignmentTarget::Identifier(declaration.identifier()), assignment.expression(), @@ -692,7 +725,15 @@ impl Transpiler { return Err(err); } }, - VariableData::ComptimeValue { value } => { + VariableData::ComptimeValue { value, read_only } => { + if *read_only { + let err = TranspileError::AssignmentError(AssignmentError { + identifier: identifier.span(), + message: "Cannot assign to a read-only value.".to_string(), + }); + handler.receive(err.clone()); + return Err(err); + } let comptime_value = expression.comptime_eval(scope, handler).map_err(|err| { let err = TranspileError::NotComptime(err); @@ -1097,9 +1138,14 @@ impl Transpiler { DataLocation::Storage { storage_name: target_storage_name, path: target_path, - r#type, + r#type: target_type, } => { - if matches!(r#type, StorageType::Boolean) { + if storage_name == target_storage_name + && path == target_path + && r#type == target_type + { + Ok(Vec::new()) + } else if matches!(target_type, StorageType::Boolean) { let cmd = Command::Raw(format!( "data modify storage {target_storage_name} {target_path} set from storage {storage_name} {path}" )); @@ -1123,10 +1169,14 @@ impl Transpiler { objective: target_objective, target: target_target, } => { - let cmd = Command::Raw(format!( - "scoreboard players operation {target_target} {target_objective} = {score_target} {objective}" - )); - Ok(vec![cmd]) + if objective == target_objective && score_target == target_target { + Ok(Vec::new()) + } else { + let cmd = Command::Raw(format!( + "scoreboard players operation {target_target} {target_objective} = {score_target} {objective}" + )); + Ok(vec![cmd]) + } } DataLocation::Storage { storage_name,