Skip to main content

rbx_rsml/datatype/
evaluate.rs

1use palette::Srgb;
2use rbx_types::{Color3uint8, Content, EnumItem, UDim, Variant};
3use rbx_types_ops::BasicOperations;
4
5use crate::lexer::Token;
6use crate::parser::types::{Construct, Delimited, Node};
7
8use crate::datatype::colors::{BRICK_COLORS, CSS_COLORS, SKIN_COLORS, TAILWIND_COLORS};
9use crate::datatype::lookup::StaticLookup;
10use crate::datatype::tuple;
11use crate::datatype::types::Datatype;
12use crate::datatype::variants::EnumItemFromNameAndValueName;
13
14pub fn evaluate_construct(
15    construct: &Construct,
16    key: Option<&str>,
17    lookup: &dyn StaticLookup,
18) -> Option<Datatype> {
19    match construct {
20        Construct::Node { node } => evaluate_token(node, key, lookup),
21
22        Construct::MathOperation {
23            left,
24            operators,
25            right,
26        } => {
27            let left_val = evaluate_construct(left, key, lookup)?;
28            let right_val = right
29                .as_ref()
30                .and_then(|r| evaluate_construct(r, key, lookup));
31
32            let Some(right_val) = right_val else {
33                return Some(left_val);
34            };
35
36            let left_variant = left_val.coerce_to_variant(key)?;
37            let right_variant = right_val.coerce_to_variant(key)?;
38
39            let result = if let Some(first_op) = operators.first() {
40                apply_operator(first_op, &left_variant, &right_variant)
41            } else {
42                None
43            };
44
45            result.map(Datatype::Variant)
46        }
47
48        Construct::UnaryMinus { operand, .. } => {
49            let val = evaluate_construct(operand, key, lookup)?;
50            let variant = val.coerce_to_variant(key)?;
51            negate_variant(&variant).map(Datatype::Variant)
52        }
53
54        Construct::Table { body } => {
55            let datatypes = evaluate_delimited_to_vec(body, lookup);
56            coerce_tuple_data(datatypes, None)
57        }
58
59        Construct::AnnotatedTable { annotation, body } => {
60            let annotation_name = match annotation.token.value() {
61                Token::Identifier(name) => Some(*name),
62                _ => None,
63            };
64
65            if let Some(body) = body {
66                let datatypes = evaluate_delimited_to_vec(body, lookup);
67                coerce_tuple_data(datatypes, annotation_name)
68            } else {
69                coerce_tuple_data(vec![], annotation_name)
70            }
71        }
72
73        Construct::Enum { name, variant, .. } => {
74            let enum_name = name.as_ref().and_then(|n| match n.token.value() {
75                Token::TagSelectorOrEnumPart(Some(s)) => Some(*s),
76                _ => None,
77            });
78
79            let enum_value = variant.as_ref().and_then(|v| match v.token.value() {
80                Token::Identifier(s) => Some(*s),
81                Token::TagSelectorOrEnumPart(Some(s)) => Some(*s),
82                Token::StateSelectorOrEnumPart(Some(s)) => Some(*s),
83                _ => None,
84            });
85
86            match (enum_name, enum_value) {
87                (Some(name), Some(value)) => EnumItem::from_name_and_value_name(name, value)
88                    .map(|item| Datatype::Variant(Variant::EnumItem(item)))
89                    .or(Some(Datatype::None)),
90                _ => Some(Datatype::None),
91            }
92        }
93
94        Construct::Assignment { right, .. } => right
95            .as_ref()
96            .and_then(|r| evaluate_construct(r, key, lookup)),
97
98        _ => None,
99    }
100}
101
102fn evaluate_token(
103    node: &Node,
104    key: Option<&str>,
105    lookup: &dyn StaticLookup,
106) -> Option<Datatype> {
107    match node.token.value() {
108        Token::Number(s) => parse_number_str(s).map(|n| Datatype::Variant(Variant::Float64(n))),
109
110        Token::NumberOffset(s) => {
111            let num_str = s.strip_suffix("px").unwrap_or(s);
112            let offset = parse_number_str(num_str).map(|n| n as i32).unwrap_or(0);
113            Some(Datatype::Variant(Variant::UDim(UDim::new(0.0, offset))))
114        }
115
116        Token::NumberScale(s) => {
117            let num_str = s.strip_suffix('%').unwrap_or(s);
118            let scale = parse_number_str(num_str).unwrap_or(0.0) / 100.0;
119            Some(Datatype::Variant(Variant::UDim(UDim::new(scale as f32, 0))))
120        }
121
122        Token::StringSingle(s) => Some(Datatype::Variant(Variant::String(s.to_string()))),
123
124        Token::StringMulti(multi) => {
125            Some(Datatype::Variant(Variant::String(multi.content.to_string())))
126        }
127
128        Token::RbxAsset(slice) => {
129            Some(Datatype::Variant(Variant::String(slice.to_string())))
130        }
131
132        Token::RbxContent(slice) => {
133            Some(Datatype::Variant(Variant::Content(Content::from(
134                slice.to_string(),
135            ))))
136        }
137
138        Token::Boolean(s) => {
139            let val = *s == "true";
140            Some(Datatype::Variant(Variant::Bool(val)))
141        }
142
143        Token::Nil => Some(Datatype::None),
144
145        Token::ColorHex(slice) => {
146            let hex = normalize_hex(slice);
147            let color: Result<Srgb<u8>, _> = hex.parse();
148            color.ok().map(|c| {
149                Datatype::Variant(Variant::Color3(
150                    Color3uint8::new(c.red, c.green, c.blue).into(),
151                ))
152            })
153        }
154
155        Token::ColorTailwind(slice) => {
156            TAILWIND_COLORS
157                .get(&slice.to_lowercase())
158                .map(|color| Datatype::Oklab(***color))
159        }
160
161        Token::ColorSkin(slice) => {
162            SKIN_COLORS
163                .get(&slice.to_lowercase())
164                .map(|color| Datatype::Oklab(***color))
165        }
166
167        Token::ColorCss(slice) => {
168            CSS_COLORS
169                .get(&slice.to_lowercase())
170                .map(|color| Datatype::Oklab(***color))
171        }
172
173        Token::ColorBrick(slice) => {
174            BRICK_COLORS
175                .get(&slice.to_lowercase())
176                .map(|color| Datatype::Oklab(***color))
177        }
178
179        Token::TokenIdentifier(attr_name) => Some(lookup.resolve_dynamic(attr_name)),
180
181        Token::StaticTokenIdentifier(static_name) => Some(lookup.resolve_static(static_name)),
182
183        Token::MacroArgIdentifier(Some(name)) => lookup.resolve_macro_arg(name, key),
184
185        Token::StateSelectorOrEnumPart(Some(value)) => {
186            if let Some(key) = key {
187                let rebinded_key = shorthand_rebind(key);
188                EnumItem::from_name_and_value_name(rebinded_key, value)
189                    .map(|item| Datatype::Variant(Variant::EnumItem(item)))
190                    .or(Some(Datatype::None))
191            } else {
192                Some(Datatype::IncompleteEnumShorthand(value.to_string()))
193            }
194        }
195
196        _ => None,
197    }
198}
199
200fn evaluate_delimited_to_vec(
201    delimited: &Delimited,
202    lookup: &dyn StaticLookup,
203) -> Vec<Datatype> {
204    let Some(content) = &delimited.content else {
205        return vec![];
206    };
207
208    content
209        .iter()
210        .filter_map(|c| evaluate_construct(c, None, lookup))
211        .collect()
212}
213
214fn coerce_tuple_data(datatypes: Vec<Datatype>, name: Option<&str>) -> Option<Datatype> {
215    let mut t = tuple::Tuple::new(name.map(|s| s.to_string()));
216    for d in datatypes {
217        t.push(d);
218    }
219    let result = t.coerce_to_datatype();
220    match result {
221        Datatype::None => None,
222        other => Some(other),
223    }
224}
225
226fn negate_variant(variant: &Variant) -> Option<Variant> {
227    match variant {
228        Variant::Float64(n) => Some(Variant::Float64(-n)),
229        Variant::UDim(udim) => Some(Variant::UDim(UDim::new(-udim.scale, -udim.offset))),
230        _ => None,
231    }
232}
233
234fn apply_operator(op_node: &Node, left: &Variant, right: &Variant) -> Option<Variant> {
235    let narrowed_right;
236    let right = match (left, right) {
237        (Variant::Float64(_), _) => right,
238        (_, Variant::Float64(n)) => {
239            narrowed_right = Variant::Float32(*n as f32);
240            &narrowed_right
241        }
242        _ => right,
243    };
244
245    match op_node.token.value() {
246        Token::OpAdd => left.add(right),
247        Token::OpSub => left.sub(right),
248        Token::OpMult => left.mult(right),
249        Token::OpDiv => left.div(right),
250        Token::OpFloorDiv => left.floor_div(right),
251        Token::OpMod => left.modulus(right),
252        Token::OpPow => left.pow(right),
253        _ => None,
254    }
255}
256
257fn normalize_hex(hex: &str) -> String {
258    let hex = hex.trim_start_matches('#');
259    match hex.len() {
260        3 | 6 => hex.into(),
261        1..=5 => format!("{:0<6}", hex),
262        _ => hex.into(),
263    }
264}
265
266const SHORTHAND_REBINDS: phf::Map<&'static str, &'static str> = phf_macros::phf_map! {
267    "FlexMode" => "UIFlexMode",
268    "HorizontalFlex" => "UIFlexAlignment",
269    "VerticalFlex" => "UIFlexAlignment",
270};
271
272pub(crate) fn shorthand_rebind<'a>(key: &'a str) -> &'a str {
273    SHORTHAND_REBINDS.get(key).copied().unwrap_or(key)
274}
275
276fn parse_number_str(s: &str) -> Option<f64> {
277    let cleaned: String = s.chars().filter(|c| *c != '_').collect();
278    cleaned.parse::<f64>().ok()
279}