lucia_lang/
meta_ops.rs

1//! The meta method used in lucia lang.
2
3use std::fmt;
4
5use gc_arena::Collect;
6
7use crate::{
8    errors::{Error, ErrorKind},
9    objects::{AnyCallback, Callback, CallbackReturn, Function, IntoValue, Str, Table, Value},
10    Context,
11};
12
13#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Collect)]
14#[collect(require_static)]
15pub enum MetaMethod {
16    Call,
17    Iter,
18    GetAttr,
19    GetItem,
20    SetAttr,
21    SetItem,
22    Neg,
23    Add,
24    Sub,
25    Mul,
26    Div,
27    Mod,
28    Eq,
29    Ne,
30    Gt,
31    Ge,
32    Lt,
33    Le,
34    Len,
35    Bool,
36    Int,
37    Float,
38    Str,
39    Repr,
40}
41
42macro_rules! meta_operator_error {
43    ($ctx:expr, $operator:expr, $arg1:expr) => {
44        Error::new(ErrorKind::MetaUnOperator {
45            operator: $operator,
46            operand: $arg1.value_type(),
47        })
48    };
49    ($ctx:expr, $operator:expr, $arg1:expr, $arg2:expr) => {
50        Error::new(ErrorKind::MetaBinOperator {
51            operator: $operator,
52            operand: ($arg1.value_type(), $arg2.value_type()),
53        })
54    };
55}
56
57impl MetaMethod {
58    pub const fn name(self) -> &'static str {
59        match self {
60            MetaMethod::Call => "__call__",
61            MetaMethod::Iter => "__iter__",
62            MetaMethod::GetAttr => "__getattr__",
63            MetaMethod::GetItem => "__getitem__",
64            MetaMethod::SetAttr => "__setattr__",
65            MetaMethod::SetItem => "__setitem__",
66            MetaMethod::Neg => "__neg__",
67            MetaMethod::Add => "__add__",
68            MetaMethod::Sub => "__sub__",
69            MetaMethod::Mul => "__mul__",
70            MetaMethod::Div => "__div__",
71            MetaMethod::Mod => "__mod__",
72            MetaMethod::Eq => "__eq__",
73            MetaMethod::Ne => "__ne__",
74            MetaMethod::Gt => "__gt__",
75            MetaMethod::Ge => "__ge__",
76            MetaMethod::Lt => "__lt__",
77            MetaMethod::Le => "__le__",
78            MetaMethod::Len => "__len__",
79            MetaMethod::Bool => "__bool__",
80            MetaMethod::Int => "__int__",
81            MetaMethod::Float => "__float__",
82            MetaMethod::Str => "__str__",
83            MetaMethod::Repr => "__repr__",
84        }
85    }
86}
87
88impl fmt::Display for MetaMethod {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        write!(f, "{}", self.name())
91    }
92}
93
94impl<'gc> IntoValue<'gc> for MetaMethod {
95    fn into_value(self, ctx: Context<'gc>) -> Value<'gc> {
96        self.name().into_value(ctx)
97    }
98}
99
100#[derive(Debug, Copy, Clone, Collect)]
101#[collect(no_drop)]
102pub enum MetaResult<'gc, const N: usize> {
103    Value(Value<'gc>),
104    Call(Function<'gc>, [Value<'gc>; N]),
105}
106
107pub fn call<'gc>(ctx: Context<'gc>, v: Value<'gc>) -> Result<Function<'gc>, Error<'gc>> {
108    if let Value::Function(f) = v {
109        return Ok(f);
110    }
111    let metatable = v
112        .metatable()
113        .ok_or_else(|| meta_operator_error!(ctx, MetaMethod::Call, v))?;
114    match metatable.get(ctx, MetaMethod::Call) {
115        Value::Function(f) => Ok(f),
116        v @ Value::Table(_) => call(ctx, v),
117        v => Err(meta_operator_error!(ctx, MetaMethod::Call, v)),
118    }
119}
120
121#[derive(Collect)]
122#[collect(no_drop)]
123pub struct IterTable<'gc>(pub Table<'gc>, pub usize);
124
125impl<'gc> Callback<'gc> for IterTable<'gc> {
126    fn call(
127        &mut self,
128        ctx: Context<'gc>,
129        _args: Vec<Value<'gc>>,
130    ) -> Result<CallbackReturn<'gc>, Error<'gc>> {
131        let t = Ok(CallbackReturn::Return(self.0.get_index(self.1).map_or(
132            Value::Null,
133            |(k, v)| {
134                let t = Table::new(&ctx);
135                t.set(ctx, 0, k);
136                t.set(ctx, 1, v);
137                t.into()
138            },
139        )));
140        self.1 += 1;
141        t
142    }
143}
144
145#[derive(Collect)]
146#[collect(no_drop)]
147pub struct MetaIterTable<'gc>(pub Function<'gc>, pub Value<'gc>);
148
149impl<'gc> Callback<'gc> for MetaIterTable<'gc> {
150    fn call(
151        &mut self,
152        _ctx: Context<'gc>,
153        _args: Vec<Value<'gc>>,
154    ) -> Result<CallbackReturn<'gc>, Error<'gc>> {
155        Ok(CallbackReturn::TailCall(self.0, vec![self.1]))
156    }
157}
158
159pub fn iter<'gc>(ctx: Context<'gc>, v: Value<'gc>) -> Result<Function<'gc>, Error<'gc>> {
160    if let Some(metatable) = v.metatable() {
161        let t = metatable.get(ctx, MetaMethod::Iter);
162        if !t.is_null() {
163            return Ok(Function::Callback(AnyCallback::new(
164                &ctx,
165                MetaIterTable(call(ctx, t)?, v),
166            )));
167        }
168    }
169
170    match v {
171        Value::Table(v) => Ok(Function::Callback(AnyCallback::new(&ctx, IterTable(v, 0)))),
172        Value::Function(v) => Ok(v),
173        _ => Err(meta_operator_error!(ctx, MetaMethod::Iter, v)),
174    }
175}
176
177macro_rules! get_table {
178    ($name:tt, $meta_method:tt) => {
179        pub fn $name<'gc>(
180            ctx: Context<'gc>,
181            table: Value<'gc>,
182            key: Value<'gc>,
183        ) -> Result<MetaResult<'gc, 2>, Error<'gc>> {
184            if let Some(metatable) = table.metatable() {
185                let t = metatable.get(ctx, MetaMethod::$meta_method);
186                if !t.is_null() {
187                    return Ok(MetaResult::Call(call(ctx, t)?, [table, key]));
188                }
189            }
190
191            match table {
192                Value::Table(v) => Ok(MetaResult::Value(v.get(ctx, key))),
193                _ => Err(meta_operator_error!(
194                    ctx,
195                    MetaMethod::$meta_method,
196                    table,
197                    key
198                )),
199            }
200        }
201    };
202}
203
204get_table!(get_attr, GetAttr);
205get_table!(get_item, GetItem);
206
207macro_rules! set_table {
208    ($name:tt, $meta_method:tt) => {
209        pub fn $name<'gc>(
210            ctx: Context<'gc>,
211            table: Value<'gc>,
212            key: Value<'gc>,
213            value: Value<'gc>,
214        ) -> Result<MetaResult<'gc, 3>, Error<'gc>> {
215            if let Some(metatable) = table.metatable() {
216                let t = metatable.get(ctx, MetaMethod::$meta_method);
217                if !t.is_null() {
218                    return Ok(MetaResult::Call(call(ctx, t)?, [table, key, value]));
219                }
220            }
221
222            match table {
223                Value::Table(v) => {
224                    v.set(ctx, key, value);
225                    Ok(MetaResult::Value(Value::Null))
226                }
227                _ => Err(meta_operator_error!(
228                    ctx,
229                    MetaMethod::$meta_method,
230                    table,
231                    key
232                )),
233            }
234        }
235    };
236}
237
238set_table!(set_attr, SetAttr);
239set_table!(set_item, SetItem);
240
241pub fn neg<'gc>(ctx: Context<'gc>, v: Value<'gc>) -> Result<MetaResult<'gc, 1>, Error<'gc>> {
242    if let Some(metatable) = v.metatable() {
243        let t = metatable.get(ctx, MetaMethod::Neg);
244        if !t.is_null() {
245            return Ok(MetaResult::Call(call(ctx, t)?, [v]));
246        }
247    }
248
249    match v {
250        Value::Int(v) => Ok(MetaResult::Value((-v).into())),
251        Value::Float(v) => Ok(MetaResult::Value((-v).into())),
252        _ => Err(meta_operator_error!(ctx, MetaMethod::Neg, v)),
253    }
254}
255
256macro_rules! bin_op {
257    ($name:tt, $op:tt, $meta_method:tt) => {
258        pub fn $name<'gc>(
259            ctx: Context<'gc>,
260            v1: Value<'gc>,
261            v2: Value<'gc>,
262        ) -> Result<MetaResult<'gc, 2>, Error<'gc>> {
263            if let Some(metatable) = v1.metatable() {
264                let t = metatable.get(ctx, MetaMethod::$meta_method);
265                if !t.is_null() {
266                    return Ok(MetaResult::Call(call(ctx, t)?, [v1, v2]));
267                }
268            }
269
270            match (v1, v2) {
271                (Value::Int(v1), Value::Int(v2)) => Ok(MetaResult::Value((v1 $op v2).into())),
272                (Value::Float(v1), Value::Float(v2)) => Ok(MetaResult::Value((v1 $op v2).into())),
273                _ => Err(meta_operator_error!(ctx, MetaMethod::$meta_method, v1, v2)),
274            }
275        }
276    };
277}
278
279pub fn add<'gc>(
280    ctx: Context<'gc>,
281    v1: Value<'gc>,
282    v2: Value<'gc>,
283) -> Result<MetaResult<'gc, 2>, Error<'gc>> {
284    if let Some(metatable) = v1.metatable() {
285        let t = metatable.get(ctx, MetaMethod::Add);
286        if !t.is_null() {
287            return Ok(MetaResult::Call(call(ctx, t)?, [v1, v2]));
288        }
289    }
290
291    match (v1, v2) {
292        (Value::Int(v1), Value::Int(v2)) => Ok(MetaResult::Value((v1 + v2).into())),
293        (Value::Float(v1), Value::Float(v2)) => Ok(MetaResult::Value((v1 + v2).into())),
294        (Value::Str(v1), Value::Str(v2)) => Ok(MetaResult::Value(Value::Str(Str::new(
295            &ctx,
296            v1.to_string() + &v2.to_string(),
297        )))),
298        _ => Err(meta_operator_error!(ctx, MetaMethod::Add, v1, v2)),
299    }
300}
301
302bin_op!(sub, -, Sub);
303bin_op!(mul, *, Mul);
304bin_op!(div, /, Div);
305bin_op!(mod_, %, Mod);
306
307macro_rules! eq_ne {
308    ($name:tt, $op:tt, $meta_method:tt) => {
309        pub fn $name<'gc>(
310            ctx: Context<'gc>,
311            tos: Value<'gc>,
312            tos1: Value<'gc>,
313        ) -> Result<MetaResult<'gc, 2>, Error<'gc>> {
314            if let Some(metatable) = tos1.metatable() {
315                let t = metatable.get(ctx, MetaMethod::$meta_method);
316                if !t.is_null() {
317                    return Ok(MetaResult::Call(call(ctx, t)?, [tos1, tos]));
318                }
319            }
320
321            Ok(MetaResult::Value((tos1 $op tos).into()))
322        }
323    };
324}
325
326eq_ne!(eq, ==, Eq);
327eq_ne!(ne, !=, Ne);
328
329bin_op!(gt, >, Gt);
330bin_op!(ge, >=, Ge);
331bin_op!(lt, <, Lt);
332bin_op!(le, <=, Le);
333
334pub fn len<'gc>(ctx: Context<'gc>, v: Value<'gc>) -> Result<MetaResult<'gc, 1>, Error<'gc>> {
335    if let Some(metatable) = v.metatable() {
336        let t = metatable.get(ctx, MetaMethod::Len);
337        if !t.is_null() {
338            return Ok(MetaResult::Call(call(ctx, t)?, [v]));
339        }
340    }
341
342    match v {
343        Value::Str(s) => Ok(MetaResult::Value(Value::Int(s.len().try_into().unwrap()))),
344        Value::Table(t) => Ok(MetaResult::Value(i64::try_from(t.len()).unwrap().into())),
345        _ => Err(meta_operator_error!(ctx, MetaMethod::Len, v)),
346    }
347}
348
349pub fn bool<'gc>(ctx: Context<'gc>, v: Value<'gc>) -> Result<MetaResult<'gc, 1>, Error<'gc>> {
350    if let Some(metatable) = v.metatable() {
351        let t = metatable.get(ctx, MetaMethod::Bool);
352        if !t.is_null() {
353            return Ok(MetaResult::Call(call(ctx, t)?, [v]));
354        }
355    }
356
357    match v {
358        Value::Null => Ok(MetaResult::Value(false.into())),
359        Value::Bool(v) => Ok(MetaResult::Value(v.into())),
360        Value::Int(v) => Ok(MetaResult::Value((v != 0).into())),
361        Value::Float(v) => Ok(MetaResult::Value((v != 0.0).into())),
362        _ => Ok(MetaResult::Value(true.into())),
363    }
364}
365
366pub fn int<'gc>(ctx: Context<'gc>, v: Value<'gc>) -> Result<MetaResult<'gc, 1>, Error<'gc>> {
367    if let Some(metatable) = v.metatable() {
368        let t = metatable.get(ctx, MetaMethod::Int);
369        if !t.is_null() {
370            return Ok(MetaResult::Call(call(ctx, t)?, [v]));
371        }
372    }
373
374    match v {
375        Value::Null => Ok(MetaResult::Value(0.into())),
376        Value::Bool(v) => Ok(MetaResult::Value((if v { 1 } else { 0 }).into())),
377        Value::Int(v) => Ok(MetaResult::Value(v.into())),
378        Value::Float(v) => Ok(MetaResult::Value((v as i64).into())),
379        Value::Str(s) => Ok(MetaResult::Value(
380            s.parse::<i64>()
381                .map_err(|_| meta_operator_error!(ctx, MetaMethod::Int, v))?
382                .into(),
383        )),
384        _ => Err(meta_operator_error!(ctx, MetaMethod::Int, v)),
385    }
386}
387
388pub fn float<'gc>(ctx: Context<'gc>, v: Value<'gc>) -> Result<MetaResult<'gc, 1>, Error<'gc>> {
389    if let Some(metatable) = v.metatable() {
390        let t = metatable.get(ctx, MetaMethod::Float);
391        if !t.is_null() {
392            return Ok(MetaResult::Call(call(ctx, t)?, [v]));
393        }
394    }
395
396    match v {
397        Value::Null => Ok(MetaResult::Value((0.0).into())),
398        Value::Bool(v) => Ok(MetaResult::Value((if v { 1.0 } else { 0.0 }).into())),
399        Value::Int(v) => Ok(MetaResult::Value((v as f64).into())),
400        Value::Float(v) => Ok(MetaResult::Value(v.into())),
401        Value::Str(s) => Ok(MetaResult::Value(
402            s.parse::<f64>()
403                .map_err(|_| meta_operator_error!(ctx, MetaMethod::Int, v))?
404                .into(),
405        )),
406        _ => Err(meta_operator_error!(ctx, MetaMethod::Float, v)),
407    }
408}
409
410pub fn str<'gc>(ctx: Context<'gc>, v: Value<'gc>) -> Result<MetaResult<'gc, 1>, Error<'gc>> {
411    if let Some(metatable) = v.metatable() {
412        let t = metatable.get(ctx, MetaMethod::Str);
413        if !t.is_null() {
414            return Ok(MetaResult::Call(call(ctx, t)?, [v]));
415        }
416    }
417
418    Ok(MetaResult::Value(v.to_string().into_value(ctx)))
419}
420
421pub fn repr<'gc>(ctx: Context<'gc>, v: Value<'gc>) -> Result<MetaResult<'gc, 1>, Error<'gc>> {
422    if let Some(metatable) = v.metatable() {
423        let t = metatable.get(ctx, MetaMethod::Repr);
424        if !t.is_null() {
425            return Ok(MetaResult::Call(call(ctx, t)?, [v]));
426        }
427    }
428
429    Ok(MetaResult::Value(v.repr().into_value(ctx)))
430}