allow marking return value from lua as containing macro

This commit is contained in:
Moritz Hölting 2025-03-15 00:22:33 +01:00
parent 8a9db43424
commit 6dde4b41c1
3 changed files with 159 additions and 8 deletions

View File

@ -10,8 +10,8 @@ mod enabled {
base::{self, source_file::SourceElement, Handler},
syntax::syntax_tree::expression::LuaCode,
transpile::{
error::{LuaRuntimeError, TranspileError, TranspileResult},
expression::ComptimeValue,
error::{LuaRuntimeError, MismatchedTypes, TranspileError, TranspileResult},
expression::{ComptimeValue, ExpectedType},
Scope, VariableData,
},
};
@ -124,7 +124,7 @@ mod enabled {
table.set("path", lua.create_string(path)?)?;
Value::Table(table)
}
Some(_) => todo!("allow other types"),
Some(_) => todo!("allow other variable types"),
None => todo!("throw correct error"),
};
globals.set(name, value)?;
@ -155,8 +155,68 @@ mod enabled {
handler,
),
Value::Boolean(boolean) => Ok(Some(ComptimeValue::Boolean(boolean))),
Value::Table(table) => match table.get::<Value>("value") {
Ok(Value::Nil) => {
let err = TranspileError::LuaRuntimeError(LuaRuntimeError {
code_block: self.span(),
error_message: "return table must contain non-nil 'value'".to_string(),
});
handler.receive(err.clone());
Err(err)
}
Ok(value) => {
let value = match self.handle_lua_result(value, handler)? {
Some(ComptimeValue::String(s)) => {
let contains_macro = match table.get::<Value>("contains_macro") {
Ok(Value::Boolean(boolean)) => Ok(boolean),
Ok(value) => {
if let Some(ComptimeValue::Boolean(boolean)) =
self.handle_lua_result(value, handler)?
{
Ok(boolean)
} else {
let err =
TranspileError::MismatchedTypes(MismatchedTypes {
expression: self.span(),
expected_type: ExpectedType::Boolean,
});
handler.receive(err.clone());
Err(err)
}
}
_ => {
let err =
TranspileError::MismatchedTypes(MismatchedTypes {
expression: self.span(),
expected_type: ExpectedType::Boolean,
});
handler.receive(err.clone());
Err(err)
}
}?;
if contains_macro {
Some(ComptimeValue::MacroString(
s.parse().expect("parsing cannot fail"),
))
} else {
Some(ComptimeValue::String(s))
}
}
value => value,
};
Ok(value)
}
Err(err) => {
let err = TranspileError::LuaRuntimeError(LuaRuntimeError::from_lua_err(
&err,
self.span(),
));
handler.receive(err.clone());
Err(err)
}
},
Value::Error(_)
| Value::Table(_)
| Value::Thread(_)
| Value::UserData(_)
| Value::LightUserData(_)

View File

@ -1,6 +1,6 @@
//! Utility methods for transpiling
use std::fmt::Display;
use std::{fmt::Display, str::FromStr};
use crate::{
lexical::token::{MacroStringLiteral, MacroStringLiteralPart},
@ -114,6 +114,61 @@ where
})
}
impl FromStr for MacroString {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
let pos = s.find("$(");
if pos.is_some_and(|pos| s[pos..].contains(')')) {
let mut parts = Vec::new();
let mut s = s;
while let Some(pos) = s.find("$(") {
let (before, after) = s.split_at(pos);
let last_macro_index = after
.char_indices()
.skip(2)
.take_while(|&(_, c)| c.is_ascii_alphanumeric() || c == '_')
.map(|(i, _)| i)
.last();
match last_macro_index {
Some(last_macro_index) if after[last_macro_index + 1..].starts_with(')') => {
if !before.is_empty() {
parts.push(MacroStringPart::String(before.to_string()));
}
parts.push(MacroStringPart::MacroUsage(
after[2..=last_macro_index].to_string(),
));
s = &after[last_macro_index + 2..];
if s.is_empty() {
break;
}
}
_ => {
parts.push(MacroStringPart::String(s.to_string()));
s = "";
break;
}
}
}
if !s.is_empty() {
parts.push(MacroStringPart::String(s.to_string()));
}
if parts
.iter()
.any(|p| matches!(p, MacroStringPart::MacroUsage(_)))
{
Ok(Self::MacroString(parts))
} else {
Ok(Self::String(s.to_string()))
}
} else {
Ok(Self::String(s.to_string()))
}
}
}
impl<S> From<S> for MacroString
where
S: Into<String>,
@ -172,3 +227,39 @@ impl From<MacroStringLiteral> for MacroString {
Self::from(&value)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_macro_string() {
assert_eq!(
MacroString::from_str("Hello, $(world)!").unwrap(),
MacroString::MacroString(vec![
MacroStringPart::String("Hello, ".to_string()),
MacroStringPart::MacroUsage("world".to_string()),
MacroStringPart::String("!".to_string())
])
);
assert_eq!(
MacroString::from_str("Hello, $(world)! $(world").unwrap(),
MacroString::MacroString(vec![
MacroStringPart::String("Hello, ".to_string()),
MacroStringPart::MacroUsage("world".to_string()),
MacroStringPart::String("! $(world".to_string()),
])
);
assert_eq!(
MacroString::from_str("Hello $(a) from $(b) and $(c)").unwrap(),
MacroString::MacroString(vec![
MacroStringPart::String("Hello ".to_string()),
MacroStringPart::MacroUsage("a".to_string()),
MacroStringPart::String(" from ".to_string()),
MacroStringPart::MacroUsage("b".to_string()),
MacroStringPart::String(" and ".to_string()),
MacroStringPart::MacroUsage("c".to_string()),
])
);
}
}

View File

@ -129,6 +129,8 @@ impl<'a> Scope<'a> {
}
/// Gets the number of times a variable has been shadowed.
///
///
pub fn get_variable_shadow_count(&self, name: &str) -> usize {
let count = self
.shadowed
@ -138,9 +140,7 @@ impl<'a> Scope<'a> {
.copied()
.unwrap_or(0);
self.parent.as_ref().map_or(count, |parent| {
count
+ parent.get_variable_shadow_count(name)
+ usize::from(parent.get_variable(name).is_some())
count.saturating_sub(1) + parent.get_variable_shadow_count(name)
})
}