diff --git a/src/lexical/token.rs b/src/lexical/token.rs index bc0f10b..0db18b8 100644 --- a/src/lexical/token.rs +++ b/src/lexical/token.rs @@ -52,6 +52,7 @@ pub enum KeywordKind { Int, Bool, Macro, + Val, } impl Display for KeywordKind { @@ -117,6 +118,7 @@ impl KeywordKind { Self::Int => "int", Self::Bool => "bool", Self::Macro => "macro", + Self::Val => "val", } } diff --git a/src/semantic/mod.rs b/src/semantic/mod.rs index 2b97797..3642408 100644 --- a/src/semantic/mod.rs +++ b/src/semantic/mod.rs @@ -396,6 +396,10 @@ impl Assignment { let expected = match variable_type { VariableType::BooleanStorage | VariableType::Tag => ValueType::Boolean, VariableType::ScoreboardValue => ValueType::Integer, + VariableType::ComptimeValue => { + // TODO: check if the expression is a constant expression + return Ok(()); + } _ => { let err = error::Error::AssignmentError(AssignmentError { identifier: self.destination().span(), @@ -445,6 +449,12 @@ impl VariableDeclaration { KeywordKind::Int => (VariableType::ScoreboardValue, ExpectedType::Integer), _ => unreachable!("variable type is not a valid type"), }, + Self::ComptimeValue(decl) => { + if let Some(assignment) = decl.assignment() { + assignment.expression().analyze_semantics(scope, handler)?; + } + (VariableType::ComptimeValue, ExpectedType::Any) + } }; scope.set_variable(name, var); let assignment = match self { @@ -459,7 +469,12 @@ impl VariableDeclaration { } } Self::Single(single) => single.assignment().as_ref(), + Self::ComptimeValue(decl) => decl.assignment().as_ref(), }; + if var == VariableType::ComptimeValue { + // TODO: check if the expression is a constant expression + return Ok(()); + } if let Some(assignment) = assignment { let expected = match var { VariableType::BooleanStorage | VariableType::Tag => ValueType::Boolean, @@ -689,8 +704,35 @@ impl Binary { handler: &impl Handler, ) -> Result<(), error::Error> { match self.operator() { - BinaryOperator::Add(_) - | BinaryOperator::Subtract(_) + BinaryOperator::Add(_) => { + if (self + .left_operand() + .can_yield_type_semantics(ValueType::Integer, scope) + && self + .right_operand() + .can_yield_type_semantics(ValueType::Integer, scope)) + || self + .left_operand() + .can_yield_type_semantics(ValueType::String, scope) + || self + .right_operand() + .can_yield_type_semantics(ValueType::String, scope) + { + self.left_operand().analyze_semantics(scope, handler)?; + self.right_operand().analyze_semantics(scope, handler) + } else { + let err = error::Error::MismatchedTypes(MismatchedTypes { + expected_type: ExpectedType::AnyOf(vec![ + ExpectedType::Integer, + ExpectedType::String, + ]), + expression: self.span(), + }); + handler.receive(err.clone()); + Err(err) + } + } + BinaryOperator::Subtract(_) | BinaryOperator::Multiply(_) | BinaryOperator::Divide(_) | BinaryOperator::Modulo(_) => { @@ -779,8 +821,8 @@ impl Binary { } impl LuaCode { - #[expect(clippy::unused_self, clippy::unnecessary_wraps)] - #[cfg_attr(feature = "lua", expect(unused_variables))] + #[expect(clippy::unused_self)] + #[cfg_attr(feature = "lua", expect(unused_variables, clippy::unnecessary_wraps))] fn analyze_semantics( &self, _scope: &SemanticScope, diff --git a/src/semantic/scope.rs b/src/semantic/scope.rs index ce1ce0f..6c9b4e3 100644 --- a/src/semantic/scope.rs +++ b/src/semantic/scope.rs @@ -21,6 +21,8 @@ pub enum VariableType { BooleanStorageArray, /// Compiler internal function. InternalFunction, + /// Compiler internal value. + ComptimeValue, } /// A scope that stores variables. diff --git a/src/syntax/syntax_tree/statement.rs b/src/syntax/syntax_tree/statement.rs index f457a75..eb567b1 100644 --- a/src/syntax/syntax_tree/statement.rs +++ b/src/syntax/syntax_tree/statement.rs @@ -325,6 +325,7 @@ pub enum VariableDeclaration { Array(ArrayVariableDeclaration), Score(ScoreVariableDeclaration), Tag(TagVariableDeclaration), + ComptimeValue(ComptimeValueDeclaration), } impl SourceElement for VariableDeclaration { @@ -334,6 +335,7 @@ impl SourceElement for VariableDeclaration { Self::Array(declaration) => declaration.span(), Self::Score(declaration) => declaration.span(), Self::Tag(declaration) => declaration.span(), + Self::ComptimeValue(declaration) => declaration.span(), } } } @@ -347,6 +349,7 @@ impl VariableDeclaration { Self::Array(declaration) => &declaration.identifier, Self::Score(declaration) => &declaration.identifier, Self::Tag(declaration) => &declaration.identifier, + Self::ComptimeValue(declaration) => &declaration.identifier, } } @@ -358,6 +361,7 @@ impl VariableDeclaration { Self::Array(declaration) => &declaration.variable_type, Self::Score(declaration) => &declaration.int_keyword, Self::Tag(declaration) => &declaration.bool_keyword, + Self::ComptimeValue(declaration) => &declaration.val_keyword, } } @@ -383,6 +387,14 @@ impl VariableDeclaration { declaration.annotations.push_front(annotation); Ok(Self::Tag(declaration)) } + Self::ComptimeValue(_) => { + let err = Error::InvalidAnnotation(InvalidAnnotation { + annotation: annotation.assignment.identifier.span, + target: "comptime values".to_string(), + }); + + Err(err) + } } } } @@ -681,6 +693,53 @@ impl TagVariableDeclaration { } } +/// Represents a compile time value declaration in the syntax tree. +/// +/// Syntax Synopsis: +/// +/// ```ebnf +/// ComptimeValueDeclaration: +/// 'val' identifier VariableDeclarationAssignment? +/// ``` +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] +pub struct ComptimeValueDeclaration { + /// The type of the variable. + #[get = "pub"] + val_keyword: Keyword, + /// The identifier of the variable. + #[get = "pub"] + identifier: Identifier, + /// The optional assignment of the variable. + #[get = "pub"] + assignment: Option, + /// The annotations of the variable declaration. + #[get = "pub"] + annotations: VecDeque, +} + +impl SourceElement for ComptimeValueDeclaration { + fn span(&self) -> Span { + self.val_keyword + .span() + .join( + &self + .assignment + .as_ref() + .map_or_else(|| self.identifier.span(), SourceElement::span), + ) + .expect("The span of the single variable declaration is invalid.") + } +} + +impl ComptimeValueDeclaration { + /// Dissolves the [`ComptimeValueDeclaration`] into its components. + #[must_use] + pub fn dissolve(self) -> (Keyword, Identifier, Option) { + (self.val_keyword, self.identifier, self.assignment) + } +} + /// Represents an assignment in the syntax tree. /// /// Syntax Synopsis: @@ -883,7 +942,10 @@ impl Parser<'_> { ) -> ParseResult { let statement = match self.stop_at_significant() { Reading::Atomic(Token::Keyword(keyword)) - if matches!(keyword.keyword, KeywordKind::Int | KeywordKind::Bool) => + if matches!( + keyword.keyword, + KeywordKind::Int | KeywordKind::Bool | KeywordKind::Val + ) => { self.parse_variable_declaration(handler) .map(SemicolonStatement::VariableDeclaration) @@ -895,18 +957,20 @@ impl Parser<'_> { let destination = { let identifier = p.parse_identifier(&VoidHandler)?; - if let Ok(tree) = p.step_into( - Delimiter::Bracket, - |pp| pp.parse_expression(&VoidHandler), - &VoidHandler, - ) { - let open = tree.open; - let close = tree.close; - let expression = tree.tree?; + match p.stop_at_significant() { + Reading::IntoDelimited(punc) if punc.punctuation == '[' => { + let tree = p.step_into( + Delimiter::Bracket, + |pp| pp.parse_expression(&VoidHandler), + &VoidHandler, + )?; + let open = tree.open; + let close = tree.close; + let expression = tree.tree?; - AssignmentDestination::Indexed(identifier, open, expression, close) - } else { - AssignmentDestination::Identifier(identifier) + AssignmentDestination::Indexed(identifier, open, expression, close) + } + _ => AssignmentDestination::Identifier(identifier), } }; let equals = p.parse_punctuation('=', true, &VoidHandler)?; @@ -948,7 +1012,10 @@ impl Parser<'_> { let variable_type = match self.stop_at_significant() { Reading::Atomic(Token::Keyword(keyword)) - if matches!(keyword.keyword, KeywordKind::Int | KeywordKind::Bool) => + if matches!( + keyword.keyword, + KeywordKind::Int | KeywordKind::Bool | KeywordKind::Val + ) => { self.forward(); keyword @@ -983,7 +1050,10 @@ impl Parser<'_> { let identifier = self.parse_identifier(handler)?; match self.stop_at_significant() { - Reading::IntoDelimited(punc) if punc.punctuation == '[' => { + Reading::IntoDelimited(punc) + if punc.punctuation == '[' + && matches!(variable_type.keyword, KeywordKind::Int | KeywordKind::Bool) => + { let tree = self.step_into( Delimiter::Bracket, |p| { @@ -1071,12 +1141,23 @@ impl Parser<'_> { let expression = self.parse_expression(handler)?; let assignment = VariableDeclarationAssignment { equals, expression }; - Ok(VariableDeclaration::Single(SingleVariableDeclaration { - variable_type, - identifier, - assignment: Some(assignment), - annotations: VecDeque::new(), - })) + if variable_type.keyword == KeywordKind::Val { + Ok(VariableDeclaration::ComptimeValue( + ComptimeValueDeclaration { + val_keyword: variable_type, + identifier, + assignment: Some(assignment), + annotations: VecDeque::new(), + }, + )) + } else { + Ok(VariableDeclaration::Single(SingleVariableDeclaration { + variable_type, + identifier, + assignment: Some(assignment), + annotations: VecDeque::new(), + })) + } } // SingleVariableDeclaration without Assignment _ => Ok(VariableDeclaration::Single(SingleVariableDeclaration { diff --git a/src/transpile/error.rs b/src/transpile/error.rs index 89d2ce5..fd9f834 100644 --- a/src/transpile/error.rs +++ b/src/transpile/error.rs @@ -46,6 +46,8 @@ pub enum TranspileError { IllegalIndexing(#[from] IllegalIndexing), #[error(transparent)] InvalidArgument(#[from] InvalidArgument), + #[error(transparent)] + NotComptime(#[from] NotComptime), } /// The result of a transpilation operation. @@ -447,3 +449,32 @@ impl Display for InvalidArgument { } impl std::error::Error for InvalidArgument {} + +/// An error that occurs when an indexing operation is not permitted. +#[derive(Debug, Clone, PartialEq, Eq, Getters)] +pub struct NotComptime { + /// The expression that cannot be evaluated at compile time. + #[get = "pub"] + pub expression: Span, +} + +impl Display for NotComptime { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + Message::new( + Severity::Error, + "The expression cannot be evaluated at compile time but is required to." + ) + )?; + + write!( + f, + "\n{}", + SourceCodeDisplay::new(&self.expression, Option::::None) + ) + } +} + +impl std::error::Error for NotComptime {} diff --git a/src/transpile/expression.rs b/src/transpile/expression.rs index 72cdb7d..a0a0cf6 100644 --- a/src/transpile/expression.rs +++ b/src/transpile/expression.rs @@ -12,7 +12,9 @@ use shulkerbox::prelude::{Command, Condition, Execute}; #[cfg(feature = "shulkerbox")] use super::{ - error::{IllegalIndexing, IllegalIndexingReason, MismatchedTypes, UnknownIdentifier}, + error::{ + IllegalIndexing, IllegalIndexingReason, MismatchedTypes, NotComptime, UnknownIdentifier, + }, Scope, TranspileResult, Transpiler, VariableData, }; #[cfg(feature = "shulkerbox")] @@ -238,12 +240,14 @@ impl Expression { } /// Evaluate at compile-time. - #[must_use] + /// + /// # Errors + /// - If the expression is not compile-time evaluatable. pub fn comptime_eval( &self, scope: &Arc, handler: &impl Handler, - ) -> Option { + ) -> Result { match self { Self::Primary(primary) => primary.comptime_eval(scope, handler), Self::Binary(binary) => binary.comptime_eval(scope, handler), @@ -333,19 +337,40 @@ impl Primary { } /// Evaluate at compile-time. - #[must_use] + /// + /// # Errors + /// - If the expression is not compile-time evaluatable. pub fn comptime_eval( &self, scope: &Arc, handler: &impl Handler, - ) -> Option { + ) -> Result { match self { - Self::Boolean(boolean) => Some(ComptimeValue::Boolean(boolean.value())), - Self::Integer(int) => Some(ComptimeValue::Integer(int.as_i64())), - Self::StringLiteral(string_literal) => Some(ComptimeValue::String( + Self::Boolean(boolean) => Ok(ComptimeValue::Boolean(boolean.value())), + Self::Integer(int) => Ok(ComptimeValue::Integer(int.as_i64())), + Self::StringLiteral(string_literal) => Ok(ComptimeValue::String( string_literal.str_content().to_string(), )), - Self::Identifier(_) | Self::FunctionCall(_) | Self::Indexed(_) => None, + Self::Identifier(ident) => scope.get_variable(ident.span.str()).map_or_else( + || { + Err(NotComptime { + expression: self.span(), + }) + }, + |var| match var.as_ref() { + VariableData::ComptimeValue { value } => { + value.read().unwrap().clone().ok_or_else(|| NotComptime { + expression: ident.span.clone(), + }) + } + _ => Err(NotComptime { + expression: self.span(), + }), + }, + ), + Self::FunctionCall(_) | Self::Indexed(_) => Err(NotComptime { + expression: self.span(), + }), Self::Parenthesized(parenthesized) => { parenthesized.expression().comptime_eval(scope, handler) } @@ -355,12 +380,14 @@ impl Primary { .comptime_eval(scope, handler) .and_then(|val| match (prefix.operator(), val) { (PrefixOperator::LogicalNot(_), ComptimeValue::Boolean(boolean)) => { - Some(ComptimeValue::Boolean(!boolean)) + Ok(ComptimeValue::Boolean(!boolean)) } (PrefixOperator::Negate(_), ComptimeValue::Integer(int)) => { - Some(ComptimeValue::Integer(-int)) + Ok(ComptimeValue::Integer(-int)) } - _ => None, + _ => Err(NotComptime { + expression: prefix.span(), + }), }) } Self::Lua(lua) => lua @@ -368,19 +395,21 @@ impl Primary { .inspect_err(|err| { handler.receive(err.clone()); }) - .ok() - .flatten(), + .map_err(|_| NotComptime { + expression: lua.span(), + }) + .and_then(|val| val), Self::MacroStringLiteral(macro_string_literal) => { if macro_string_literal .parts() .iter() .any(|part| matches!(part, MacroStringLiteralPart::MacroUsage { .. })) { - Some(ComptimeValue::MacroString( + Ok(ComptimeValue::MacroString( macro_string_literal.clone().into(), )) } else { - Some(ComptimeValue::String(macro_string_literal.str_content())) + Ok(ComptimeValue::String(macro_string_literal.str_content())) } } } @@ -430,12 +459,14 @@ impl Binary { } /// Evaluate at compile-time. - #[must_use] + /// + /// # Errors + /// - If the expression is not compile-time evaluatable. pub fn comptime_eval( &self, scope: &Arc, handler: &impl Handler, - ) -> Option { + ) -> Result { let left = self.left_operand().comptime_eval(scope, handler)?; let right = self.right_operand().comptime_eval(scope, handler)?; @@ -449,7 +480,7 @@ impl Binary { .right_operand() .can_yield_type(ValueType::Boolean, scope) => { - Some(ComptimeValue::Boolean(true)) + Ok(ComptimeValue::Boolean(true)) } (ComptimeValue::Boolean(false), _) | (_, ComptimeValue::Boolean(false)) if matches!(self.operator(), BinaryOperator::LogicalAnd(..)) @@ -460,15 +491,17 @@ impl Binary { .right_operand() .can_yield_type(ValueType::Boolean, scope) => { - Some(ComptimeValue::Boolean(false)) + Ok(ComptimeValue::Boolean(false)) } (ComptimeValue::Boolean(left), ComptimeValue::Boolean(right)) => { match self.operator() { - BinaryOperator::Equal(..) => Some(ComptimeValue::Boolean(left == right)), - BinaryOperator::NotEqual(..) => Some(ComptimeValue::Boolean(left != right)), - BinaryOperator::LogicalAnd(..) => Some(ComptimeValue::Boolean(left && right)), - BinaryOperator::LogicalOr(..) => Some(ComptimeValue::Boolean(left || right)), - _ => None, + BinaryOperator::Equal(..) => Ok(ComptimeValue::Boolean(left == right)), + BinaryOperator::NotEqual(..) => Ok(ComptimeValue::Boolean(left != right)), + BinaryOperator::LogicalAnd(..) => Ok(ComptimeValue::Boolean(left && right)), + BinaryOperator::LogicalOr(..) => Ok(ComptimeValue::Boolean(left || right)), + _ => Err(NotComptime { + expression: self.span(), + }), } } (ComptimeValue::Integer(left), ComptimeValue::Integer(right)) => { @@ -498,14 +531,37 @@ impl Binary { } _ => None, } + .ok_or_else(|| NotComptime { + expression: self.span(), + }) } (ComptimeValue::String(left), ComptimeValue::String(right)) => match self.operator() { - BinaryOperator::Add(..) => Some(ComptimeValue::String(left + &right)), - BinaryOperator::Equal(..) => Some(ComptimeValue::Boolean(left == right)), - BinaryOperator::NotEqual(..) => Some(ComptimeValue::Boolean(left != right)), - _ => None, + BinaryOperator::Add(..) => Ok(ComptimeValue::String(left + &right)), + BinaryOperator::Equal(..) => Ok(ComptimeValue::Boolean(left == right)), + BinaryOperator::NotEqual(..) => Ok(ComptimeValue::Boolean(left != right)), + _ => Err(NotComptime { + expression: self.span(), + }), }, - _ => None, + // TODO: also allow macro strings + ( + left @ ComptimeValue::String(_), + right @ (ComptimeValue::Boolean(_) | ComptimeValue::Integer(_)), + ) + | ( + left @ (ComptimeValue::Boolean(_) | ComptimeValue::Integer(_)), + right @ ComptimeValue::String(_), + ) => match self.operator() { + BinaryOperator::Add(_) => Ok(ComptimeValue::String( + left.to_string_no_macro().unwrap() + &right.to_string_no_macro().unwrap(), + )), + _ => Err(NotComptime { + expression: self.span(), + }), + }, + _ => Err(NotComptime { + expression: self.span(), + }), } } } @@ -641,7 +697,7 @@ impl Transpiler { Primary::Lua(lua) => { #[expect(clippy::option_if_let_else)] - if let Some(value) = lua.eval_comptime(scope, handler)? { + if let Ok(value) = lua.eval_comptime(scope, handler)? { self.store_comptime_value(&value, target, lua, handler) } else { let err = TranspileError::MissingValue(MissingValue { @@ -803,7 +859,7 @@ impl Transpiler { if let Some(variable) = variable.as_deref() { let from = match variable { VariableData::Scoreboard { objective } => { - if let Some(ComptimeValue::String(target)) = + if let Ok(ComptimeValue::String(target)) = indexed.index().comptime_eval(scope, handler) { Ok(DataLocation::ScoreboardValue { @@ -822,7 +878,7 @@ impl Transpiler { } } VariableData::ScoreboardArray { objective, targets } => { - if let Some(ComptimeValue::Integer(index)) = + if let Ok(ComptimeValue::Integer(index)) = indexed.index().comptime_eval(scope, handler) { if let Some(target) = usize::try_from(index) @@ -860,7 +916,7 @@ impl Transpiler { storage_name, paths, } => { - if let Some(ComptimeValue::Integer(index)) = + if let Ok(ComptimeValue::Integer(index)) = indexed.index().comptime_eval(scope, handler) { if let Some(path) = usize::try_from(index) @@ -924,7 +980,7 @@ impl Transpiler { scope: &Arc, handler: &impl Handler, ) -> TranspileResult> { - if let Some(value) = binary.comptime_eval(scope, handler) { + if let Ok(value) = binary.comptime_eval(scope, handler) { self.store_comptime_value(&value, target, binary, handler) } else { match binary.operator() { @@ -1087,7 +1143,7 @@ impl Transpiler { storage_name, paths, } => { - if let Some(ComptimeValue::Integer(index)) = + if let Ok(ComptimeValue::Integer(index)) = indexed.index().comptime_eval(scope, handler) { if let Some(path) = usize::try_from(index) @@ -1168,15 +1224,15 @@ impl Transpiler { } }, Primary::Lua(lua) => match lua.eval_comptime(scope, handler)? { - Some(ComptimeValue::String(value)) => Ok(( + Ok(ComptimeValue::String(value)) => Ok(( Vec::new(), ExtendedCondition::Runtime(Condition::Atom(value.into())), )), - Some(ComptimeValue::MacroString(value)) => Ok(( + Ok(ComptimeValue::MacroString(value)) => Ok(( Vec::new(), ExtendedCondition::Runtime(Condition::Atom(value.into())), )), - Some(ComptimeValue::Boolean(boolean)) => { + Ok(ComptimeValue::Boolean(boolean)) => { Ok((Vec::new(), ExtendedCondition::Comptime(boolean))) } _ => { diff --git a/src/transpile/function.rs b/src/transpile/function.rs index fde399a..e2eb30e 100644 --- a/src/transpile/function.rs +++ b/src/transpile/function.rs @@ -102,6 +102,7 @@ impl Transpiler { TranspileAnnotationValue::None => Ok(identifier_span.str().to_string()), TranspileAnnotationValue::Expression(expr) => expr .comptime_eval(scope, handler) + .ok() .and_then(|val| val.to_string_no_macro()) .ok_or_else(|| { let err = TranspileError::IllegalAnnotationContent( @@ -293,13 +294,10 @@ impl Transpiler { let value = match expression { Expression::Primary(Primary::Lua(lua)) => { lua.eval_comptime(scope, handler).and_then(|val| match val { - Some(ComptimeValue::MacroString(s)) => Ok(Parameter::Static(s)), - Some(val) => Ok(Parameter::Static(val.to_macro_string())), - None => { - let err = TranspileError::MismatchedTypes(MismatchedTypes { - expression: expression.span(), - expected_type: ExpectedType::String, - }); + Ok(ComptimeValue::MacroString(s)) => Ok(Parameter::Static(s)), + Ok(val) => Ok(Parameter::Static(val.to_macro_string())), + Err(err) => { + let err = TranspileError::NotComptime(err); handler.receive(err.clone()); Err(err) } diff --git a/src/transpile/internal_functions.rs b/src/transpile/internal_functions.rs index ebd9a96..fb0dfad 100644 --- a/src/transpile/internal_functions.rs +++ b/src/transpile/internal_functions.rs @@ -183,17 +183,17 @@ fn print_function( let args = get_args_assert_in_range(call, 1..=2)?; let first = args.first().expect("checked range"); - let (target, message_expression) = args.get(1).map_or_else( - || ("@a".into(), first), - |second| { - ( - first - .comptime_eval(scope, &VoidHandler) - .map_or_else(|| "@a".into(), |val| val.to_macro_string()), - second, - ) - }, - ); + let (target, message_expression) = if let Some(second) = args.get(1) { + ( + first + .comptime_eval(scope, &VoidHandler) + .map(|val| val.to_macro_string()) + .map_err(TranspileError::NotComptime)?, + second, + ) + } else { + ("@a".into(), first) + }; let mut contains_macro = matches!(target, MacroString::MacroString(_)); @@ -240,7 +240,7 @@ fn print_function( Primary::Identifier(ident) => { match scope.get_variable(ident.span.str()).as_deref() { Some(VariableData::Scoreboard { objective }) => { - if let Some(ComptimeValue::String(index)) = + if let Ok(ComptimeValue::String(index)) = indexed.index().comptime_eval(scope, &VoidHandler) { let (cmd, value) = get_data_location( @@ -262,7 +262,7 @@ fn print_function( } } Some(VariableData::ScoreboardArray { objective, targets }) => { - if let Some(ComptimeValue::Integer(index)) = + if let Ok(ComptimeValue::Integer(index)) = indexed.index().comptime_eval(scope, &VoidHandler) { #[expect(clippy::option_if_let_else)] @@ -300,7 +300,7 @@ fn print_function( storage_name, paths, }) => { - if let Some(ComptimeValue::Integer(index)) = + if let Ok(ComptimeValue::Integer(index)) = indexed.index().comptime_eval(scope, &VoidHandler) { #[expect(clippy::option_if_let_else)] diff --git a/src/transpile/lua.rs b/src/transpile/lua.rs index bc792d0..27fa353 100644 --- a/src/transpile/lua.rs +++ b/src/transpile/lua.rs @@ -12,8 +12,8 @@ mod enabled { syntax::syntax_tree::expression::LuaCode, transpile::{ error::{ - InvalidArgument, LuaRuntimeError, MismatchedTypes, TranspileError, TranspileResult, - UnknownIdentifier, + InvalidArgument, LuaRuntimeError, MismatchedTypes, NotComptime, TranspileError, + TranspileResult, UnknownIdentifier, }, expression::{ComptimeValue, ExpectedType}, Scope, VariableData, @@ -86,11 +86,15 @@ mod enabled { &self, scope: &Arc, handler: &impl Handler, - ) -> TranspileResult> { + ) -> TranspileResult> { // required to keep the lua instance alive let (lua_result, _lua) = self.eval(scope, handler)?; - self.handle_lua_result(lua_result, handler) + self.handle_lua_result(lua_result, handler).map(|res| { + res.ok_or_else(|| NotComptime { + expression: self.span(), + }) + }) } fn add_globals(&self, lua: &Lua, scope: &Arc) -> TranspileResult<()> { @@ -262,6 +266,22 @@ mod enabled { .map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?; Value::Table(table) } + Some(VariableData::ComptimeValue { value }) => { + let value = value.read().unwrap(); + match &*value { + Some(ComptimeValue::Boolean(b)) => Value::Boolean(*b), + Some(ComptimeValue::Integer(i)) => Value::Integer(*i), + Some(ComptimeValue::String(s)) => Value::String( + lua.create_string(s) + .map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?, + ), + Some(ComptimeValue::MacroString(s)) => Value::String( + lua.create_string(s.to_string()) + .map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?, + ), + None => Value::Nil, + } + } Some(VariableData::Function { .. } | VariableData::InternalFunction { .. }) => { // TODO: add support for functions return Err(TranspileError::InvalidArgument(InvalidArgument { diff --git a/src/transpile/transpiler.rs b/src/transpile/transpiler.rs index 69c95af..edac539 100644 --- a/src/transpile/transpiler.rs +++ b/src/transpile/transpiler.rs @@ -25,7 +25,7 @@ use crate::{ }; use super::{ - error::{MismatchedTypes, TranspileError, TranspileResult}, + error::{MismatchedTypes, MissingValue, TranspileError, TranspileResult, UnknownIdentifier}, expression::{ComptimeValue, ExpectedType, ExtendedCondition}, variables::{Scope, TranspileAssignmentTarget, VariableData}, FunctionData, TranspileAnnotationValue, TranspiledFunctionArguments, @@ -342,11 +342,46 @@ impl Transpiler { Expression::Primary(Primary::FunctionCall(func)) => { self.transpile_function_call(func, scope, handler) } + 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(_) => { + let error = TranspileError::UnexpectedExpression(UnexpectedExpression( + expression.clone(), + )); + handler.receive(error.clone()); + Err(error) + } + None => { + let error = TranspileError::UnknownIdentifier(UnknownIdentifier { + identifier: ident.span.clone(), + }); + handler.receive(error.clone()); + Err(error) + } + } + } expression @ Expression::Primary( Primary::Integer(_) | Primary::Boolean(_) | Primary::Prefix(_) - | Primary::Identifier(_) | Primary::Indexed(_), ) => { let error = @@ -361,9 +396,9 @@ impl Transpiler { Ok(vec![Command::UsesMacro(string.into())]) } Expression::Primary(Primary::Lua(code)) => match code.eval_comptime(scope, handler)? { - Some(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]), - Some(ComptimeValue::MacroString(cmd)) => Ok(vec![Command::UsesMacro(cmd.into())]), - Some(ComptimeValue::Boolean(_) | ComptimeValue::Integer(_)) => { + Ok(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]), + Ok(ComptimeValue::MacroString(cmd)) => Ok(vec![Command::UsesMacro(cmd.into())]), + Ok(ComptimeValue::Boolean(_) | ComptimeValue::Integer(_)) => { let err = TranspileError::MismatchedTypes(MismatchedTypes { expected_type: ExpectedType::String, expression: code.span(), @@ -371,7 +406,13 @@ impl Transpiler { handler.receive(err.clone()); Err(err) } - None => Ok(Vec::new()), + Err(_) => { + let err = TranspileError::MissingValue(MissingValue { + expression: code.span(), + }); + handler.receive(err.clone()); + Err(err) + } }, Expression::Primary(Primary::Parenthesized(parenthesized)) => self @@ -382,8 +423,8 @@ impl Transpiler { handler, ), Expression::Binary(bin) => match bin.comptime_eval(scope, handler) { - Some(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]), - Some(ComptimeValue::MacroString(cmd)) => Ok(vec![Command::UsesMacro(cmd.into())]), + Ok(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]), + Ok(ComptimeValue::MacroString(cmd)) => Ok(vec![Command::UsesMacro(cmd.into())]), _ => { let err = TranspileError::MismatchedTypes(MismatchedTypes { expression: bin.span(), @@ -621,7 +662,7 @@ impl Transpiler { } }); - if let Some(ComptimeValue::Boolean(value)) = cond_expression.comptime_eval(scope, handler) { + if let Ok(ComptimeValue::Boolean(value)) = cond_expression.comptime_eval(scope, handler) { if value { Ok(Some((Vec::new(), then))) } else { diff --git a/src/transpile/variables.rs b/src/transpile/variables.rs index dd88872..a256a74 100644 --- a/src/transpile/variables.rs +++ b/src/transpile/variables.rs @@ -30,7 +30,7 @@ use crate::{ use super::{ error::{ AssignmentError, IllegalAnnotationContent, IllegalIndexing, IllegalIndexingReason, - MismatchedTypes, + MismatchedTypes, NotComptime, }, expression::{ComptimeValue, DataLocation, ExpectedType, StorageType}, FunctionData, TranspileAnnotationValue, TranspileError, TranspileResult, @@ -100,6 +100,11 @@ pub enum VariableData { /// The implementation implementation: InternalFunction, }, + /// Compiler internal variable. + ComptimeValue { + /// The value. + value: Arc>>, + }, } #[derive(Debug, Clone, Copy, EnumAsInner)] @@ -282,6 +287,31 @@ impl Transpiler { scope, handler, ), + VariableDeclaration::ComptimeValue(declaration) => { + let value = if let Some(assignment) = declaration.assignment() { + Some( + assignment + .expression() + .comptime_eval(scope, handler) + .map_err(|err| { + let err = TranspileError::NotComptime(err); + handler.receive(err.clone()); + err + })?, + ) + } else { + None + }; + + scope.set_variable( + declaration.identifier().span.str(), + VariableData::ComptimeValue { + value: Arc::new(RwLock::new(value)), + }, + ); + + Ok(Vec::new()) + } } } @@ -466,7 +496,7 @@ impl Transpiler { let (identifier, indexing_value) = match destination { TranspileAssignmentTarget::Identifier(ident) => (ident, None), TranspileAssignmentTarget::Indexed(ident, expression) => { - (ident, expression.comptime_eval(scope, handler)) + (ident, expression.comptime_eval(scope, handler).ok()) } }; if let Some(target) = scope.get_variable(identifier.span.str()) { @@ -662,6 +692,16 @@ impl Transpiler { return Err(err); } }, + VariableData::ComptimeValue { value } => { + let comptime_value = + expression.comptime_eval(scope, handler).map_err(|err| { + let err = TranspileError::NotComptime(err); + handler.receive(err.clone()); + err + })?; + *value.write().unwrap() = Some(comptime_value); + return Ok(Vec::new()); + } VariableData::Function { .. } | VariableData::MacroParameter { .. } | VariableData::InternalFunction { .. } => { @@ -723,6 +763,7 @@ impl Transpiler { TranspileAnnotationValue::Expression(expr) => { if let Some(name_eval) = expr .comptime_eval(scope, handler) + .ok() .and_then(|val| val.to_string_no_macro()) { // TODO: change invalid criteria if boolean @@ -821,9 +862,11 @@ impl Transpiler { if let (Some(name_eval), Some(target_eval)) = ( objective .comptime_eval(scope, handler) + .ok() .and_then(|val| val.to_string_no_macro()), target .comptime_eval(scope, handler) + .ok() .and_then(|val| val.to_string_no_macro()), ) { // TODO: change invalid criteria if boolean