implement more missing functionality marked with todo!

This commit is contained in:
Moritz Hölting 2025-03-12 13:38:27 +01:00
parent f6dadf881a
commit 5740172ddb
5 changed files with 439 additions and 191 deletions

View File

@ -38,7 +38,7 @@ path-absolutize = "3.1.1"
pathdiff = "0.2.3" pathdiff = "0.2.3"
serde = { version = "1.0.217", features = ["derive"], optional = true } serde = { version = "1.0.217", features = ["derive"], optional = true }
# shulkerbox = { version = "0.1.0", default-features = false, optional = true } # shulkerbox = { version = "0.1.0", default-features = false, optional = true }
shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", rev = "c36c87c3db311cea0b373501c370c12edc6d051f", default-features = false, optional = true } shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", rev = "e9f2b9b91d72322ec2e063ce7b83415071306468", default-features = false, optional = true }
strsim = "0.11.1" strsim = "0.11.1"
strum = { version = "0.27.0", features = ["derive"] } strum = { version = "0.27.0", features = ["derive"] }
thiserror = "2.0.11" thiserror = "2.0.11"

View File

@ -24,7 +24,6 @@ use super::{
#[cfg(feature = "shulkerbox")] #[cfg(feature = "shulkerbox")]
use crate::{ use crate::{
base::{self, source_file::SourceElement, Handler}, base::{self, source_file::SourceElement, Handler},
semantic::error::UnexpectedExpression,
transpile::{error::FunctionArgumentsNotAllowed, TranspileError}, transpile::{error::FunctionArgumentsNotAllowed, TranspileError},
}; };
@ -166,7 +165,7 @@ impl Expression {
pub fn can_yield_type(&self, r#type: ValueType, scope: &Arc<Scope>) -> bool { pub fn can_yield_type(&self, r#type: ValueType, scope: &Arc<Scope>) -> bool {
match self { match self {
Self::Primary(primary) => primary.can_yield_type(r#type, scope), Self::Primary(primary) => primary.can_yield_type(r#type, scope),
Self::Binary(_binary) => todo!(), Self::Binary(binary) => binary.can_yield_type(r#type, scope),
} }
} }
@ -217,10 +216,21 @@ impl Primary {
&& prefix.operand().can_yield_type(r#type, scope) && prefix.operand().can_yield_type(r#type, scope)
} }
}, },
// TODO: Add support for Lua. Self::Lua(lua) => {
#[expect(clippy::match_same_arms)] if let Ok(value) = lua.eval(&VoidHandler) {
Self::Lua(_) => false, match value {
Self::StringLiteral(_) | Self::MacroStringLiteral(_) => false, mlua::Value::Boolean(_) => matches!(r#type, ValueType::Boolean),
mlua::Value::Integer(_) => matches!(r#type, ValueType::Integer),
mlua::Value::String(_) => matches!(r#type, ValueType::String),
_ => false,
}
} else {
false
}
}
Self::StringLiteral(_) | Self::MacroStringLiteral(_) => {
matches!(r#type, ValueType::String | ValueType::Boolean)
}
} }
} }
@ -246,12 +256,8 @@ impl Primary {
_ => None, _ => None,
} }
}), }),
// TODO: correctly evaluate lua code // TODO: throw error
Self::Lua(lua) => lua Self::Lua(lua) => lua.eval_comptime(&VoidHandler).ok().flatten(),
.eval_string(&VoidHandler)
.ok()
.flatten()
.map(ComptimeValue::String),
Self::MacroStringLiteral(macro_string_literal) => { Self::MacroStringLiteral(macro_string_literal) => {
if macro_string_literal if macro_string_literal
.parts() .parts()
@ -270,6 +276,46 @@ impl Primary {
} }
impl Binary { impl Binary {
/// Returns whether the binary can yield a certain type.
#[must_use]
pub fn can_yield_type(&self, r#type: ValueType, scope: &Arc<Scope>) -> bool {
match self.operator() {
BinaryOperator::Add(_) => {
matches!(r#type, ValueType::Integer | ValueType::String)
&& self.left_operand().can_yield_type(r#type, scope)
&& self.right_operand().can_yield_type(r#type, scope)
}
BinaryOperator::Subtract(_)
| BinaryOperator::Multiply(_)
| BinaryOperator::Divide(_)
| BinaryOperator::Modulo(_) => {
matches!(r#type, ValueType::Integer)
&& self.left_operand().can_yield_type(r#type, scope)
&& self.right_operand().can_yield_type(r#type, scope)
}
BinaryOperator::Equal(..) | BinaryOperator::NotEqual(..) => {
matches!(r#type, ValueType::Boolean)
}
BinaryOperator::GreaterThan(_)
| BinaryOperator::GreaterThanOrEqual(..)
| BinaryOperator::LessThan(_)
| BinaryOperator::LessThanOrEqual(..) => {
matches!(r#type, ValueType::Boolean)
&& self
.left_operand()
.can_yield_type(ValueType::Integer, scope)
&& self
.right_operand()
.can_yield_type(ValueType::Integer, scope)
}
BinaryOperator::LogicalAnd(..) | BinaryOperator::LogicalOr(..) => {
matches!(r#type, ValueType::Boolean)
&& self.left_operand().can_yield_type(r#type, scope)
&& self.right_operand().can_yield_type(r#type, scope)
}
}
}
/// Evaluate at compile-time. /// Evaluate at compile-time.
#[must_use] #[must_use]
pub fn comptime_eval(&self, scope: &Arc<Scope>) -> Option<ComptimeValue> { pub fn comptime_eval(&self, scope: &Arc<Scope>) -> Option<ComptimeValue> {
@ -388,40 +434,12 @@ impl Transpiler {
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> TranspileResult<Vec<Command>> { ) -> TranspileResult<Vec<Command>> {
match primary { match primary {
Primary::Boolean(boolean) => match target { Primary::Boolean(boolean) => self.store_comptime_value(
DataLocation::Tag { tag_name, entity } => { &ComptimeValue::Boolean(boolean.value()),
let cmd = format!( target,
"tag {target} {op} {tag}", boolean,
target = entity, handler,
op = if boolean.value() { "add" } else { "remove" }, ),
tag = tag_name
);
Ok(vec![shulkerbox::prelude::Command::Raw(cmd)])
}
DataLocation::Storage {
storage_name,
path,
r#type,
} => {
let cmd = format!(
"data modify storage {storage} {path} set value {value}{suffix}",
storage = storage_name,
path = path,
value = if boolean.value() { "1" } else { "0" },
suffix = r#type.suffix()
);
Ok(vec![shulkerbox::prelude::Command::Raw(cmd)])
}
DataLocation::ScoreboardValue { objective, target } => {
let cmd = format!(
"scoreboard players set {target} {objective} {value}",
target = target,
objective = objective,
value = if boolean.value() { "1" } else { "0" }
);
Ok(vec![shulkerbox::prelude::Command::Raw(cmd)])
}
},
Primary::FunctionCall(func) => match target { Primary::FunctionCall(func) => match target {
DataLocation::ScoreboardValue { objective, target } => { DataLocation::ScoreboardValue { objective, target } => {
let call_cmd = self.transpile_function_call(func, scope, handler)?; let call_cmd = self.transpile_function_call(func, scope, handler)?;
@ -456,13 +474,15 @@ impl Transpiler {
.as_ref() .as_ref()
.is_some_and(|args| !args.is_empty()) .is_some_and(|args| !args.is_empty())
{ {
Err(TranspileError::FunctionArgumentsNotAllowed( let err = TranspileError::FunctionArgumentsNotAllowed(
FunctionArgumentsNotAllowed { FunctionArgumentsNotAllowed {
arguments: func.arguments().as_ref().unwrap().span(), arguments: func.arguments().as_ref().unwrap().span(),
message: "Assigning results to a tag does not support arguments." message: "Assigning results to a tag does not support arguments."
.into(), .into(),
}, },
)) );
handler.receive(err.clone());
Err(err)
} else { } else {
let prepare_cmd = Command::Raw(format!("tag {entity} remove {tag_name}")); let prepare_cmd = Command::Raw(format!("tag {entity} remove {tag_name}"));
let success_cmd = Command::Raw(format!("tag {entity} add {tag_name}")); let success_cmd = Command::Raw(format!("tag {entity} add {tag_name}"));
@ -482,60 +502,46 @@ impl Transpiler {
} }
} }
}, },
Primary::Integer(int) => match target { Primary::Integer(int) => self.store_comptime_value(
DataLocation::ScoreboardValue { objective, target } => { &ComptimeValue::Integer(int.as_i64()),
Ok(vec![Command::Raw(format!( target,
"scoreboard players set {target} {objective} {value}", int,
target = target, handler,
objective = objective, ),
value = int.as_i64()
))])
}
DataLocation::Tag { .. } => Err(TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: ValueType::Boolean,
expression: primary.span(),
})),
DataLocation::Storage {
storage_name,
path,
r#type,
} => {
if matches!(
r#type,
StorageType::Byte
| StorageType::Double
| StorageType::Int
| StorageType::Long
) {
Ok(vec![Command::Raw(format!(
"data modify storage {storage} {path} set value {value}{suffix}",
storage = storage_name,
path = path,
value = int.as_i64(),
suffix = r#type.suffix()
))])
} else {
Err(TranspileError::MismatchedTypes(MismatchedTypes {
expression: primary.span(),
expected_type: ValueType::Integer,
}))
}
}
},
Primary::Parenthesized(parenthesized) => { Primary::Parenthesized(parenthesized) => {
self.transpile_expression(parenthesized.expression(), target, scope, handler) self.transpile_expression(parenthesized.expression(), target, scope, handler)
} }
Primary::Lua(_) => { Primary::Lua(lua) => {
// TODO: Add support for Lua. if let Some(value) = lua.eval_comptime(handler)? {
Err(TranspileError::UnexpectedExpression(UnexpectedExpression( self.store_comptime_value(&value, target, lua, handler)
Expression::Primary(primary.clone()), } else {
))) todo!("handle no return value from lua")
}
} }
Primary::StringLiteral(_) | Primary::MacroStringLiteral(_) => { Primary::StringLiteral(_) | Primary::MacroStringLiteral(_) => {
Err(TranspileError::MismatchedTypes(MismatchedTypes { if matches!(
target,
DataLocation::Storage {
r#type: StorageType::Boolean,
..
} | DataLocation::Tag { .. }
) {
let (mut cmds, cond) =
self.transpile_primary_expression_as_condition(primary, scope, handler)?;
let store_cmds =
self.store_condition_success(cond, target, primary, handler)?;
cmds.extend(store_cmds);
Ok(cmds)
} else {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: target.value_type(), expected_type: target.value_type(),
expression: primary.span(), expression: primary.span(),
})) });
handler.receive(err.clone());
Err(err)
}
} }
Primary::Prefix(prefix) => match prefix.operator() { Primary::Prefix(prefix) => match prefix.operator() {
PrefixOperator::Negate(_) => match target { PrefixOperator::Negate(_) => match target {
@ -544,7 +550,7 @@ impl Transpiler {
target: score_target, target: score_target,
} => { } => {
let mut expr_cmds = self.transpile_primary_expression( let mut expr_cmds = self.transpile_primary_expression(
prefix.operand(), dbg!(prefix).operand(),
target, target,
scope, scope,
handler, handler,
@ -555,9 +561,65 @@ impl Transpiler {
Ok(expr_cmds) Ok(expr_cmds)
} }
_ => todo!("Negate operator for other types"), DataLocation::Storage {
storage_name,
path,
r#type,
} if matches!(
r#type,
StorageType::Byte | StorageType::Int | StorageType::Long
) =>
{
let (target_objective, mut targets) = self.get_temp_scoreboard_locations(1);
let target_ident = targets.pop().expect("at least size 1");
let score_to_storage_cmd = Command::Execute(Execute::Store(
format!(
"result storage {storage_name} {path} {t} 1.0",
t = r#type.as_str()
)
.into(),
Box::new(Execute::Run(Box::new(Command::Raw(format!(
"scoreboard players get {target_ident} {target_objective} "
))))),
));
let mut scoreboard_cmds = self.transpile_primary_expression(
primary,
&DataLocation::ScoreboardValue {
objective: target_objective,
target: target_ident,
}, },
PrefixOperator::LogicalNot(_) => todo!("Logical not operator"), scope,
handler,
)?;
scoreboard_cmds.push(score_to_storage_cmd);
Ok(scoreboard_cmds)
}
_ => {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expression: prefix.span(),
expected_type: ValueType::Integer,
});
handler.receive(err.clone());
Err(err)
}
},
PrefixOperator::LogicalNot(_) => {
let (mut cmds, cond) = self
.transpile_primary_expression_as_condition(primary, scope, handler)
.inspect_err(|err| {
dbg!(err);
})?;
let store_cmds =
self.store_condition_success(cond, target, primary, handler)?;
cmds.extend(store_cmds);
Ok(cmds)
}
}, },
Primary::Identifier(ident) => { Primary::Identifier(ident) => {
let variable = scope.get_variable(ident.span.str()); let variable = scope.get_variable(ident.span.str());
@ -708,56 +770,8 @@ impl Transpiler {
let (mut cmds, cond) = let (mut cmds, cond) =
self.transpile_binary_expression_as_condition(binary, scope, handler)?; self.transpile_binary_expression_as_condition(binary, scope, handler)?;
let (success_cmd, else_cmd) = match target { let store_cmds = self.store_condition_success(cond, target, binary, handler)?;
DataLocation::ScoreboardValue { objective, target } => ( cmds.extend(store_cmds);
format!("scoreboard players set {target} {objective} 1"),
format!("scoreboard players set {target} {objective} 0"),
),
DataLocation::Storage {
storage_name,
path,
r#type,
} => {
if matches!(r#type, StorageType::Boolean) {
(
format!(
"data modify storage {storage_name} {path} set value 1b"
),
format!(
"data modify storage {storage_name} {path} set value 0b"
),
)
} else {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: ValueType::Boolean,
expression: binary.span(),
});
handler.receive(err.clone());
return Err(err);
}
}
DataLocation::Tag { tag_name, entity } => (
format!("tag {entity} add {tag_name}"),
format!("tag {entity} remove {tag_name}"),
),
};
let cmd = match cond {
ExtendedCondition::Runtime(cond) => Command::Execute(Execute::If(
cond,
Box::new(Execute::Run(Box::new(Command::Raw(success_cmd)))),
Some(Box::new(Execute::Run(Box::new(Command::Raw(else_cmd))))),
)),
ExtendedCondition::Comptime(cond) => {
if cond {
Command::Raw(success_cmd)
} else {
Command::Raw(else_cmd)
}
}
};
cmds.push(cmd);
Ok(cmds) Ok(cmds)
} }
@ -904,7 +918,21 @@ impl Transpiler {
Err(err) Err(err)
} }
}, },
Primary::Lua(_) => todo!("Lua code as condition"), Primary::Lua(lua) => {
match lua.eval_comptime(handler)? {
Some(ComptimeValue::String(value) | ComptimeValue::MacroString(value)) => {
// TODO: mark condition as containing macro if so
Ok((
Vec::new(),
ExtendedCondition::Runtime(Condition::Atom(value.into())),
))
}
Some(ComptimeValue::Boolean(boolean)) => {
Ok((Vec::new(), ExtendedCondition::Comptime(boolean)))
}
_ => todo!("invalid or none lua return value"),
}
}
} }
} }
@ -1011,7 +1039,7 @@ impl Transpiler {
StorageType::Byte | StorageType::Double | StorageType::Int | StorageType::Long => { StorageType::Byte | StorageType::Double | StorageType::Int | StorageType::Long => {
Some(Command::Execute(Execute::Store( Some(Command::Execute(Execute::Store(
format!( format!(
"result storage {storage_name} {path} {t} 1", "result storage {storage_name} {path} {t} 1.0",
t = r#type.as_str() t = r#type.as_str()
) )
.into(), .into(),
@ -1228,22 +1256,175 @@ impl Transpiler {
suffix = r#type.suffix(), suffix = r#type.suffix(),
))]) ))])
} else { } else {
Err(TranspileError::MismatchedTypes(MismatchedTypes { let err = TranspileError::MismatchedTypes(MismatchedTypes {
expression: original.span(), expression: original.span(),
expected_type: target.value_type(), expected_type: target.value_type(),
})) });
handler.receive(err.clone());
Err(err)
} }
} }
}, },
ComptimeValue::String(_) | ComptimeValue::MacroString(_) => { ComptimeValue::String(_) | ComptimeValue::MacroString(_) => {
Err(TranspileError::MismatchedTypes(MismatchedTypes { let err = TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: target.value_type(), expected_type: target.value_type(),
expression: original.span(), expression: original.span(),
})) });
handler.receive(err.clone());
Err(err)
} }
} }
} }
fn store_comptime_value(
&mut self,
value: &ComptimeValue,
target: &DataLocation,
source: &impl SourceElement,
handler: &impl Handler<base::Error>,
) -> TranspileResult<Vec<Command>> {
match value {
ComptimeValue::Integer(int) => match target {
DataLocation::ScoreboardValue { objective, target } => {
Ok(vec![Command::Raw(format!(
"scoreboard players set {target} {objective} {value}",
target = target,
objective = objective,
value = int
))])
}
DataLocation::Tag { .. } => {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: ValueType::Boolean,
expression: source.span(),
});
handler.receive(err.clone());
Err(err)
}
DataLocation::Storage {
storage_name,
path,
r#type,
} => {
if matches!(
r#type,
StorageType::Byte
| StorageType::Double
| StorageType::Int
| StorageType::Long
) {
Ok(vec![Command::Raw(format!(
"data modify storage {storage} {path} set value {value}{suffix}",
storage = storage_name,
path = path,
value = int,
suffix = r#type.suffix()
))])
} else {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expression: source.span(),
expected_type: ValueType::Integer,
});
handler.receive(err.clone());
Err(err)
}
}
},
&ComptimeValue::Boolean(boolean) => match target {
DataLocation::Tag { tag_name, entity } => {
let cmd = format!(
"tag {target} {op} {tag}",
target = entity,
op = if boolean { "add" } else { "remove" },
tag = tag_name
);
Ok(vec![shulkerbox::prelude::Command::Raw(cmd)])
}
DataLocation::Storage {
storage_name,
path,
r#type,
} => {
let cmd = format!(
"data modify storage {storage} {path} set value {value}{suffix}",
storage = storage_name,
path = path,
value = if boolean { "1" } else { "0" },
suffix = r#type.suffix()
);
Ok(vec![shulkerbox::prelude::Command::Raw(cmd)])
}
DataLocation::ScoreboardValue { objective, target } => {
let cmd = format!(
"scoreboard players set {target} {objective} {value}",
target = target,
objective = objective,
value = if boolean { "1" } else { "0" }
);
Ok(vec![shulkerbox::prelude::Command::Raw(cmd)])
}
},
ComptimeValue::String(_) | ComptimeValue::MacroString(_) => {
todo!("store string comptime value")
}
}
}
fn store_condition_success(
&mut self,
cond: ExtendedCondition,
target: &DataLocation,
source: &impl SourceElement,
handler: &impl Handler<base::Error>,
) -> TranspileResult<Vec<Command>> {
let (success_cmd, else_cmd) = match target {
DataLocation::ScoreboardValue { objective, target } => (
format!("scoreboard players set {target} {objective} 1"),
format!("scoreboard players set {target} {objective} 0"),
),
DataLocation::Storage {
storage_name,
path,
r#type,
} => {
if matches!(r#type, StorageType::Boolean) {
(
format!("data modify storage {storage_name} {path} set value 1b"),
format!("data modify storage {storage_name} {path} set value 0b"),
)
} else {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: ValueType::Boolean,
expression: source.span(),
});
handler.receive(err.clone());
return Err(err);
}
}
DataLocation::Tag { tag_name, entity } => (
format!("tag {entity} add {tag_name}"),
format!("tag {entity} remove {tag_name}"),
),
};
let cmd = match cond {
ExtendedCondition::Runtime(cond) => Command::Execute(Execute::If(
cond,
Box::new(Execute::Run(Box::new(Command::Raw(success_cmd)))),
Some(Box::new(Execute::Run(Box::new(Command::Raw(else_cmd))))),
)),
ExtendedCondition::Comptime(cond) => {
if cond {
Command::Raw(success_cmd)
} else {
Command::Raw(else_cmd)
}
}
};
Ok(vec![cmd])
}
/// Get temporary scoreboard locations. /// Get temporary scoreboard locations.
fn get_temp_scoreboard_locations(&mut self, amount: usize) -> (String, Vec<String>) { fn get_temp_scoreboard_locations(&mut self, amount: usize) -> (String, Vec<String>) {
let objective = "shu_temp_".to_string() let objective = "shu_temp_".to_string()

View File

@ -7,19 +7,19 @@ mod enabled {
use crate::{ use crate::{
base::{self, source_file::SourceElement, Handler}, base::{self, source_file::SourceElement, Handler},
syntax::syntax_tree::expression::LuaCode, syntax::syntax_tree::expression::LuaCode,
transpile::error::{LuaRuntimeError, TranspileError, TranspileResult}, transpile::{
error::{LuaRuntimeError, TranspileError, TranspileResult},
expression::ComptimeValue,
},
}; };
impl LuaCode { impl LuaCode {
/// Evaluates the Lua code and returns the resulting command. /// Evaluated the Lua code and returns the resulting value.
/// ///
/// # Errors /// # Errors
/// - If Lua code evaluation is disabled. /// - If evaluation fails
#[tracing::instrument(level = "debug", name = "eval_lua", skip_all, ret)] #[tracing::instrument(level = "debug", name = "eval_lua", skip_all, ret)]
pub fn eval_string( pub fn eval(&self, handler: &impl Handler<base::Error>) -> TranspileResult<mlua::Value> {
&self,
handler: &impl Handler<base::Error>,
) -> TranspileResult<Option<String>> {
tracing::debug!("Evaluating Lua code"); tracing::debug!("Evaluating Lua code");
let lua = Lua::new(); let lua = Lua::new();
@ -48,8 +48,7 @@ mod enabled {
self.add_globals(&lua).unwrap(); self.add_globals(&lua).unwrap();
let lua_result = lua lua.load(self.code())
.load(self.code())
.set_name(name) .set_name(name)
.eval::<Value>() .eval::<Value>()
.map_err(|err| { .map_err(|err| {
@ -57,7 +56,19 @@ mod enabled {
TranspileError::from(LuaRuntimeError::from_lua_err(&err, self.span())); TranspileError::from(LuaRuntimeError::from_lua_err(&err, self.span()));
handler.receive(crate::Error::from(err.clone())); handler.receive(crate::Error::from(err.clone()));
err err
})?; })
}
/// Evaluates the Lua code and returns the resulting [`ComptimeValue`].
///
/// # Errors
/// - If Lua code evaluation is disabled.
#[tracing::instrument(level = "debug", name = "eval_lua", skip_all, ret)]
pub fn eval_comptime(
&self,
handler: &impl Handler<base::Error>,
) -> TranspileResult<Option<ComptimeValue>> {
let lua_result = self.eval(handler)?;
self.handle_lua_result(lua_result).inspect_err(|err| { self.handle_lua_result(lua_result).inspect_err(|err| {
handler.receive(err.clone()); handler.receive(err.clone());
@ -77,28 +88,32 @@ mod enabled {
Ok(()) Ok(())
} }
fn handle_lua_result(&self, value: Value) -> TranspileResult<Option<String>> { fn handle_lua_result(&self, value: Value) -> TranspileResult<Option<ComptimeValue>> {
match value { match value {
Value::Nil => Ok(None), Value::Nil => Ok(None),
Value::String(s) => Ok(Some(s.to_string_lossy())), Value::String(s) => Ok(Some(ComptimeValue::String(s.to_string_lossy()))),
Value::Integer(i) => Ok(Some(i.to_string())), Value::Integer(i) => Ok(Some(ComptimeValue::Integer(i))),
Value::Number(n) => Ok(Some(n.to_string())), // TODO: change when floating point comptime numbers are supported
Value::Number(n) => Ok(Some(ComptimeValue::String(n.to_string()))),
Value::Function(f) => self.handle_lua_result(f.call(()).map_err(|err| { Value::Function(f) => self.handle_lua_result(f.call(()).map_err(|err| {
TranspileError::LuaRuntimeError(LuaRuntimeError::from_lua_err( TranspileError::LuaRuntimeError(LuaRuntimeError::from_lua_err(
&err, &err,
self.span(), self.span(),
)) ))
})?), })?),
Value::Boolean(_) Value::Boolean(boolean) => Ok(Some(ComptimeValue::Boolean(boolean))),
| Value::Error(_) Value::Error(_)
| Value::Table(_) | Value::Table(_)
| Value::Thread(_) | Value::Thread(_)
| Value::UserData(_) | Value::UserData(_)
| Value::LightUserData(_) | Value::LightUserData(_)
| Value::Other(..) => Err(TranspileError::LuaRuntimeError(LuaRuntimeError { | Value::Other(..) => {
let err = TranspileError::LuaRuntimeError(LuaRuntimeError {
code_block: self.span(), code_block: self.span(),
error_message: format!("invalid return type {}", value.type_name()), error_message: format!("invalid return type {}", value.type_name()),
})), });
todo!("pass error to handler: {err}")
}
} }
} }
} }
@ -112,16 +127,30 @@ mod disabled {
transpile::error::{TranspileError, TranspileResult}, transpile::error::{TranspileError, TranspileResult},
}; };
use super::expression::ComptimeValue;
impl LuaCode { impl LuaCode {
/// Will always return an error because Lua code evaluation is disabled.
/// Enable the feature `lua` to enable Lua code evaluation.
///
/// # Errors
/// - Always, as the lua feature is disabled
#[tracing::instrument(level = "debug", name = "eval_lua", skip_all, ret)]
pub fn eval(&self, handler: &impl Handler<base::Error>) -> TranspileResult<mlua::Value> {
handler.receive(TranspileError::LuaDisabled);
tracing::error!("Lua code evaluation is disabled");
Err(TranspileError::LuaDisabled)
}
/// Will always return an error because Lua code evaluation is disabled. /// Will always return an error because Lua code evaluation is disabled.
/// Enable the feature `lua` to enable Lua code evaluation. /// Enable the feature `lua` to enable Lua code evaluation.
/// ///
/// # Errors /// # Errors
/// - If Lua code evaluation is disabled. /// - If Lua code evaluation is disabled.
pub fn eval_string( pub fn eval_comptime(
&self, &self,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> TranspileResult<Option<String>> { ) -> TranspileResult<Option<ComptimeValue>> {
handler.receive(TranspileError::LuaDisabled); handler.receive(TranspileError::LuaDisabled);
tracing::error!("Lua code evaluation is disabled"); tracing::error!("Lua code evaluation is disabled");
Err(TranspileError::LuaDisabled) Err(TranspileError::LuaDisabled)

View File

@ -30,8 +30,8 @@ use crate::{
}; };
use super::{ use super::{
error::{TranspileError, TranspileResult}, error::{MismatchedTypes, TranspileError, TranspileResult},
expression::{ComptimeValue, ExtendedCondition}, expression::{ComptimeValue, ExtendedCondition, ValueType},
variables::{Scope, VariableData}, variables::{Scope, VariableData},
FunctionData, TranspileAnnotationValue, FunctionData, TranspileAnnotationValue,
}; };
@ -419,7 +419,19 @@ impl Transpiler {
_ => unreachable!("Function call should always return a raw command"), _ => unreachable!("Function call should always return a raw command"),
}), }),
Expression::Primary(Primary::Lua(lua)) => { Expression::Primary(Primary::Lua(lua)) => {
lua.eval_string(handler).map(Option::unwrap_or_default) lua.eval_comptime(handler).and_then(|opt| {
opt.map_or_else(
|| {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expression: expression.span(),
expected_type: ValueType::String,
});
handler.receive(err.clone());
Err(err)
},
|val| Ok(val.to_string()),
)
})
} }
Expression::Primary(Primary::Integer(num)) => Ok(num.span.str().to_string()), Expression::Primary(Primary::Integer(num)) => Ok(num.span.str().to_string()),
Expression::Primary(Primary::Boolean(bool)) => Ok(bool.span.str().to_string()), Expression::Primary(Primary::Boolean(bool)) => Ok(bool.span.str().to_string()),
@ -597,9 +609,22 @@ impl Transpiler {
Expression::Primary(Primary::MacroStringLiteral(string)) => { Expression::Primary(Primary::MacroStringLiteral(string)) => {
Ok(vec![Command::UsesMacro(string.into())]) Ok(vec![Command::UsesMacro(string.into())])
} }
Expression::Primary(Primary::Lua(code)) => Ok(code Expression::Primary(Primary::Lua(code)) => match code.eval_comptime(handler)? {
.eval_string(handler)? Some(ComptimeValue::String(cmd) | ComptimeValue::MacroString(cmd)) => {
.map_or_else(Vec::new, |cmd| vec![Command::Raw(cmd)])), // TODO: mark command as containing macro if so
Ok(vec![Command::Raw(cmd)])
}
Some(ComptimeValue::Boolean(_) | ComptimeValue::Integer(_)) => {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: ValueType::String,
expression: code.span(),
});
handler.receive(err.clone());
Err(err)
}
None => Ok(Vec::new()),
},
Expression::Primary(Primary::Parenthesized(parenthesized)) => self Expression::Primary(Primary::Parenthesized(parenthesized)) => self
.transpile_run_expression( .transpile_run_expression(
parenthesized.expression(), parenthesized.expression(),
@ -607,7 +632,16 @@ impl Transpiler {
scope, scope,
handler, handler,
), ),
Expression::Binary(_) => todo!("transpile binary expression in run statement"), Expression::Binary(bin) => {
if let Some(ComptimeValue::String(cmd) | ComptimeValue::MacroString(cmd)) =
bin.comptime_eval(scope)
{
// TODO: mark as containing macro if so
Ok(vec![Command::Raw(cmd)])
} else {
todo!("run binary expression")
}
}
} }
} }

View File

@ -269,7 +269,7 @@ impl Transpiler {
}, },
); );
} }
_ => todo!("implement other variable types"), _ => unreachable!("no other variable types"),
} }
single.assignment().as_ref().map_or_else( single.assignment().as_ref().map_or_else(
@ -306,7 +306,7 @@ impl Transpiler {
}) })
} }
VariableData::Function { .. } | VariableData::FunctionArgument { .. } => { VariableData::Function { .. } | VariableData::FunctionArgument { .. } => {
Err(TranspileError::AssignmentError(AssignmentError { let err = TranspileError::AssignmentError(AssignmentError {
identifier: identifier.span(), identifier: identifier.span(),
message: format!( message: format!(
"Cannot assign to a {}.", "Cannot assign to a {}.",
@ -316,16 +316,20 @@ impl Transpiler {
"function argument" "function argument"
} }
), ),
})) });
handler.receive(err.clone());
Err(err)
} }
_ => todo!("implement other variable types"), _ => todo!("implement other variable types"),
}?; }?;
self.transpile_expression(expression, &data_location, scope, handler) self.transpile_expression(expression, &data_location, scope, handler)
} else { } else {
Err(TranspileError::AssignmentError(AssignmentError { let err = TranspileError::AssignmentError(AssignmentError {
identifier: identifier.span(), identifier: identifier.span(),
message: "Variable does not exist.".to_string(), message: "Variable does not exist.".to_string(),
})) });
handler.receive(err.clone());
Err(err)
} }
} }
} }