1use 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}