koto_runtime/types/object.rs
1use crate::{Borrow, BorrowMut, ErrorKind, PtrMut, Result, prelude::*};
2use std::{any::Any, fmt, marker::PhantomData, ops::Deref};
3
4/// A trait for specifying a Koto object's type
5///
6/// Using `#[derive(KotoType)]` is recommended.
7pub trait KotoType {
8 /// The Object's type as a static string
9 fn type_static() -> &'static str
10 where
11 Self: Sized;
12
13 /// The type of the Object as a [KString]
14 ///
15 /// This should defer to the type returned by [KotoType::type_static],
16 /// and will be called whenever the object's type is needed by the runtime,
17 /// e.g. when a script calls `koto.type`, so caching the result is a good idea.
18 /// `#[derive(KotoType)]` takes care of the details here.
19 fn type_string(&self) -> KString;
20}
21
22/// A trait for defining how objects should behave when copied in the Koto runtime
23///
24/// Use `#[derive(KotoCopy)]` for simple objects that don't need a custom implementation of
25/// [KotoCopy::deep_copy].
26pub trait KotoCopy {
27 /// How the object should behave when called from `koto.copy`
28 ///
29 /// A default implementation can't be provided here, but a typical implementation will look
30 /// similar to: `Object::from(self.clone())`
31 fn copy(&self) -> KObject;
32
33 /// How the object should behave when called from `koto.deep_copy`
34 ///
35 /// Deep copies should ensure that deep copies are performed for any Koto values that are owned
36 /// by the object (see [KValue::deep_copy]).
37 fn deep_copy(&self) -> KObject {
38 self.copy()
39 }
40}
41
42/// A trait that allows objects to support '.' accesses
43///
44/// This is the mechanism for attaching custom methods to objects in the Koto runtime.
45///
46/// The `#[koto_impl]` macro provides an easy way to declare methods that should be made available
47/// via '.' access by using the `#[koto_method]` attribute, and then derives an appropriate
48/// implementation of [KotoEntries].
49pub trait KotoEntries {
50 /// Returns an optional [KMap] containing entries that can be accessed via the '.' operator.
51 ///
52 /// Implementations should return a clone of a cached map. `None` is returned by default.
53 fn entries(&self) -> Option<KMap> {
54 None
55 }
56}
57
58/// A trait for implementing objects that can be added to the Koto runtime
59///
60/// [`KotoObject`]s are added to the Koto runtime by the [KObject] type, and stored as
61/// [`KValue::Object`]s.
62///
63/// ## Example
64///
65/// ```
66/// use koto_runtime::{derive::*, prelude::*, Result};
67///
68/// #[derive(Clone, Default, KotoType, KotoCopy)]
69/// pub struct Foo {
70/// data: i32,
71/// }
72///
73/// // The `#[koto_impl]` macro derives an implementation of [KotoEntries] containing wrapper
74/// // functions for each impl function tagged with `#[koto_method]`.
75/// #[koto_impl(runtime = koto_runtime)]
76/// impl Foo {
77/// // Simple methods tagged with `#[koto_method]` can use a `&self` argument.
78/// #[koto_method(alias = "data")]
79/// fn get_data(&self) -> KValue {
80/// self.data.into()
81/// }
82///
83/// // An example of a more complex method that makes use of [MethodContext] to return the
84/// // instance as the result, which allows for chaining of setter operations. e.g.:
85/// // ```koto
86/// // make_foo(42)
87/// // .set_data(99)
88/// // .set_data(-1)
89/// // .get_data()
90/// // # -1
91/// // ```
92/// #[koto_method]
93/// fn set_data(ctx: MethodContext<Self>) -> Result<KValue> {
94/// match ctx.args {
95/// [KValue::Number(n)] => ctx.instance_mut()?.data = n.into(),
96/// unexpected => return unexpected_args("|Number|", unexpected),
97/// }
98///
99/// // Return the object instance as the result of the setter operation
100/// ctx.instance_result()
101/// }
102/// }
103///
104/// impl KotoObject for Foo {
105/// fn display(&self, ctx: &mut DisplayContext) -> Result<()> {
106/// ctx.append(format!("Foo({})", self.data));
107/// Ok(())
108/// }
109/// }
110/// ```
111///
112/// See also: [KObject].
113pub trait KotoObject: KotoType + KotoCopy + KotoEntries + KotoSend + KotoSync + Any {
114 /// Called when the object should be displayed as a string, e.g. by `io.print`
115 ///
116 /// By default, the object's type is used as the display string.
117 ///
118 /// The [`DisplayContext`] is used to append strings to the result, and provides information
119 /// about how the contents should be formatted,
120 /// e.g. the value is in a container, or the result should be displayed with debug information.
121 fn display(&self, ctx: &mut DisplayContext) -> Result<()> {
122 ctx.append(self.type_string());
123 Ok(())
124 }
125
126 /// Called for indexing operations, e.g. `x[0]`
127 ///
128 /// See also: [KotoObject::size]
129 fn index(&self, index: &KValue) -> Result<KValue> {
130 let _ = index;
131 unimplemented_error("@index", self.type_string())
132 }
133
134 /// Called when mutating an object via indexing, e.g. `x[0] = 99`
135 ///
136 /// See also: [KotoObject::size]
137 fn index_mut(&mut self, index: &KValue, value: &KValue) -> Result<()> {
138 let _ = (index, value);
139 unimplemented_error("@index_mut", self.type_string())
140 }
141
142 /// Called when checking for the number of elements contained in the object
143 ///
144 /// The size should represent the maximum valid index that can be passed to
145 /// [`KotoObject::index`].
146 ///
147 /// The runtime defers to this function when the 'size' of an object is needed,
148 /// e.g. when `koto.size` is called, or when unpacking function arguments.
149 ///
150 /// The `Indexable` type hint will pass for objects with a defined size.
151 ///
152 /// See also: [`KotoObject::index`]
153 fn size(&self) -> Option<usize> {
154 None
155 }
156
157 /// Declares to the runtime whether or not the object is callable
158 ///
159 /// The `Callable` type hint defers to the function, expecting `true` to be returned for objects
160 /// that implement [`KotoObject::call`].
161 fn is_callable(&self) -> bool {
162 false
163 }
164
165 /// Allows the object to behave as a function
166 ///
167 /// Objects that implement `call` should return `true` from [`KotoObject::is_callable`].
168 fn call(&mut self, ctx: &mut CallContext) -> Result<KValue> {
169 let _ = ctx;
170 unimplemented_error("@||", self.type_string())
171 }
172
173 /// Defines the behavior of negation (e.g. `-x`)
174 fn negate(&self) -> Result<KValue> {
175 unimplemented_error("@negate", self.type_string())
176 }
177
178 /// The `+` addition operator
179 ///
180 /// This will be called by the runtime when the object is on the LHS.
181 ///
182 /// To specialize the behaviour of `+` when the object is on the RHS, see [Self::add_rhs].
183 fn add(&self, other: &KValue) -> Result<KValue> {
184 let _ = other;
185 unimplemented_error("@+", self.type_string())
186 }
187
188 /// The `+` addition operator when the object is on the RHS
189 ///
190 /// This will be called when the value on the LHS doesn't implement the operation.
191 fn add_rhs(&self, other: &KValue) -> Result<KValue> {
192 let _ = other;
193 unimplemented_error("@+", self.type_string())
194 }
195
196 /// The `-` subtraction operator
197 ///
198 /// This will be called by the runtime when the object is on the LHS of the operation,
199 /// or as a fallback if the value on the LHS doesn't support the operation.
200 ///
201 /// To specialize the behaviour of `-` when the object is on the RHS, see [Self::subtract_rhs].
202 fn subtract(&self, other: &KValue) -> Result<KValue> {
203 let _ = other;
204 unimplemented_error("@-", self.type_string())
205 }
206
207 /// The `-` subtraction operator when the object is on the RHS
208 ///
209 /// This will be called when the value on the LHS doesn't implement the operation.
210 fn subtract_rhs(&self, other: &KValue) -> Result<KValue> {
211 let _ = other;
212 unimplemented_error("@-", self.type_string())
213 }
214
215 /// The `*` multiplication operator
216 ///
217 /// This will be called by the runtime when the object is on the LHS.
218 ///
219 /// To specialize the behaviour of `*` when the object is on the RHS, see [Self::multiply_rhs].
220 fn multiply(&self, other: &KValue) -> Result<KValue> {
221 let _ = other;
222 unimplemented_error("@*", self.type_string())
223 }
224
225 /// The `*` multiplication operator when the object is on the RHS
226 ///
227 /// This will be called when the value on the LHS doesn't implement the operation.
228 fn multiply_rhs(&self, other: &KValue) -> Result<KValue> {
229 let _ = other;
230 unimplemented_error("@*", self.type_string())
231 }
232
233 /// The `/` division operator
234 fn divide(&self, other: &KValue) -> Result<KValue> {
235 let _ = other;
236 unimplemented_error("@/", self.type_string())
237 }
238
239 /// The `/` division operator when the object is on the RHS
240 ///
241 /// This will be called when the value on the LHS doesn't implement the operation.
242 fn divide_rhs(&self, other: &KValue) -> Result<KValue> {
243 let _ = other;
244 unimplemented_error("@/", self.type_string())
245 }
246
247 /// The `%` remainder operator
248 fn remainder(&self, other: &KValue) -> Result<KValue> {
249 let _ = other;
250 unimplemented_error("@%", self.type_string())
251 }
252
253 /// The `%` remainder operator when the object is on the RHS
254 ///
255 /// This will be called when the value on the LHS doesn't implement the operation.
256 fn remainder_rhs(&self, other: &KValue) -> Result<KValue> {
257 let _ = other;
258 unimplemented_error("@%", self.type_string())
259 }
260
261 /// The `^` power operator
262 fn power(&self, other: &KValue) -> Result<KValue> {
263 let _ = other;
264 unimplemented_error("@^", self.type_string())
265 }
266
267 /// The `^` power operator when the object is on the RHS
268 ///
269 /// This will be called when the value on the LHS doesn't implement the operation.
270 fn power_rhs(&self, other: &KValue) -> Result<KValue> {
271 let _ = other;
272 unimplemented_error("@^", self.type_string())
273 }
274
275 /// The `+=` in-place addition operator
276 fn add_assign(&mut self, other: &KValue) -> Result<()> {
277 let _ = other;
278 unimplemented_error("@+=", self.type_string())
279 }
280
281 /// The `-=` in-place subtraction operator
282 fn subtract_assign(&mut self, other: &KValue) -> Result<()> {
283 let _ = other;
284 unimplemented_error("@-=", self.type_string())
285 }
286
287 /// The `*=` in-place multiplication operator
288 fn multiply_assign(&mut self, other: &KValue) -> Result<()> {
289 let _ = other;
290 unimplemented_error("@*=", self.type_string())
291 }
292
293 /// The `/=` in-place division operator
294 fn divide_assign(&mut self, other: &KValue) -> Result<()> {
295 let _ = other;
296 unimplemented_error("@/=", self.type_string())
297 }
298
299 /// The `%=` in-place remainder operator
300 fn remainder_assign(&mut self, other: &KValue) -> Result<()> {
301 let _ = other;
302 unimplemented_error("@%=", self.type_string())
303 }
304
305 /// The `^=` in-place remainder operator
306 fn power_assign(&mut self, other: &KValue) -> Result<()> {
307 let _ = other;
308 unimplemented_error("@^=", self.type_string())
309 }
310
311 /// The `<` less-than operator
312 fn less(&self, other: &KValue) -> Result<bool> {
313 let _ = other;
314 unimplemented_error("@<", self.type_string())
315 }
316
317 /// The `<=` less-than-or-equal operator
318 ///
319 /// The default implementation derives its result from [Self::less] and [Self::equal].
320 fn less_or_equal(&self, other: &KValue) -> Result<bool> {
321 match self.less(other) {
322 Ok(true) => Ok(true),
323 Ok(false) => match self.equal(other) {
324 Ok(result) => Ok(result),
325 Err(error) if error.is_unimplemented_error() => {
326 unimplemented_error("@<=", self.type_string())
327 }
328 error => error,
329 },
330 Err(error) if error.is_unimplemented_error() => {
331 unimplemented_error("@<=", self.type_string())
332 }
333 error => error,
334 }
335 }
336
337 /// The `>` greater-than operator
338 ///
339 /// The default implementation derives its result from [Self::less] and [Self::equal].
340 fn greater(&self, other: &KValue) -> Result<bool> {
341 match self.less(other) {
342 Ok(true) => Ok(false),
343 Ok(false) => match self.equal(other) {
344 Ok(result) => Ok(!result),
345 Err(error) if error.is_unimplemented_error() => {
346 unimplemented_error("@>", self.type_string())
347 }
348 error => error,
349 },
350 Err(error) if error.is_unimplemented_error() => {
351 unimplemented_error("@>", self.type_string())
352 }
353 error => error,
354 }
355 }
356
357 /// The `>=` greater-than-or-equal operator
358 ///
359 /// The default implementation derives its result from [Self::less].
360 fn greater_or_equal(&self, other: &KValue) -> Result<bool> {
361 match self.less(other) {
362 Ok(result) => Ok(!result),
363 Err(error) if error.is_unimplemented_error() => {
364 unimplemented_error("@>=", self.type_string())
365 }
366 error => error,
367 }
368 }
369
370 /// The `==` equality operator
371 fn equal(&self, other: &KValue) -> Result<bool> {
372 let _ = other;
373 unimplemented_error("@==", self.type_string())
374 }
375
376 /// The `!=` inequality operator
377 ///
378 /// The default implementation derives its result from [Self::equal].
379 fn not_equal(&self, other: &KValue) -> Result<bool> {
380 match self.equal(other) {
381 Ok(result) => Ok(!result),
382 Err(error) if error.is_unimplemented_error() => {
383 unimplemented_error("@!=", self.type_string())
384 }
385 error => error,
386 }
387 }
388
389 /// Declares to the runtime whether or not the object is iterable
390 ///
391 /// The `Iterable` type hint defers to this function,
392 /// accepting anything other than `IsIterable::NotIterable`.
393 fn is_iterable(&self) -> IsIterable {
394 IsIterable::NotIterable
395 }
396
397 /// Returns an iterator that iterates over the objects contents
398 ///
399 /// If [`IsIterable::Iterable`] is returned from [`is_iterable`](Self::is_iterable),
400 /// then the runtime will call this function when the object is used in iterable contexts,
401 /// expecting a [`KIterator`] to be returned.
402 fn make_iterator(&self, vm: &mut KotoVm) -> Result<KIterator> {
403 let _ = vm;
404 unimplemented_error("@iterator", self.type_string())
405 }
406
407 /// Gets the object's next value in an iteration
408 ///
409 /// If either [`ForwardIterator`][IsIterable::ForwardIterator] or
410 /// [`BidirectionalIterator`][IsIterable::BidirectionalIterator] is returned from
411 /// [is_iterable](Self::is_iterable), then the object will be wrapped in a [`KIterator`]
412 /// whenever it's used in an iterable context. This function will then be called each time
413 /// [`KIterator::next`] is invoked.
414 fn iterator_next(&mut self, vm: &mut KotoVm) -> Option<KIteratorOutput> {
415 let _ = vm;
416 None
417 }
418
419 /// Gets the object's next value from the end of an iteration
420 ///
421 /// If [`BidirectionalIterator`][IsIterable::BidirectionalIterator] is returned from
422 /// [`is_iterable`](Self::is_iterable), then the object will be wrapped in a [`KIterator`]
423 /// whenever it's used in an iterable context. This function will then be called each time
424 /// [`KIterator::next_back`] is invoked.
425 fn iterator_next_back(&mut self, vm: &mut KotoVm) -> Option<KIteratorOutput> {
426 let _ = vm;
427 None
428 }
429
430 /// Converts the object into a serializable [KValue]
431 ///
432 /// This is called by `koto_serde`'s serialize implementation when the object is encountered
433 /// during serialization.
434 ///
435 /// The object should prepare a [KValue] that best represents the object's properties.
436 fn serialize(&self) -> Result<KValue> {
437 unimplemented_error("serialize", self.type_string())
438 }
439}
440
441/// A [`KotoObject`] wrapper used in the Koto runtime
442#[derive(Clone)]
443pub struct KObject {
444 object: PtrMut<dyn KotoObject>,
445}
446
447impl KObject {
448 /// Checks if the object is of the given type
449 pub fn is_a<T: KotoObject>(&self) -> bool {
450 match self.object.try_borrow() {
451 Some(object) => (object.deref() as &dyn Any).is::<T>(),
452 None => false,
453 }
454 }
455
456 /// Attempts to borrow the underlying object immutably
457 pub fn try_borrow(&self) -> Result<Borrow<'_, dyn KotoObject>> {
458 self.object
459 .try_borrow()
460 .ok_or_else(|| ErrorKind::UnableToBorrowObject.into())
461 }
462
463 /// Attempts to borrow the underlying object mutably
464 pub fn try_borrow_mut(&self) -> Result<BorrowMut<'_, dyn KotoObject>> {
465 self.object
466 .try_borrow_mut()
467 .ok_or_else(|| ErrorKind::UnableToBorrowObject.into())
468 }
469
470 /// Attempts to immutably borrow and cast the underlying object to the specified type
471 pub fn cast<T: KotoObject>(&self) -> Result<Borrow<'_, T>> {
472 Borrow::filter_map(self.try_borrow()?, |object| {
473 (object as &dyn Any).downcast_ref::<T>()
474 })
475 .map_err(|_| match self.try_borrow() {
476 Ok(object) => ErrorKind::UnexpectedObjectType {
477 expected: T::type_static(),
478 unexpected: object.type_string(),
479 }
480 .into(),
481 Err(e) => e,
482 })
483 }
484
485 /// Attempts to mutably borrow and cast the underlying object to the specified type
486 pub fn cast_mut<T: KotoObject>(&self) -> Result<BorrowMut<'_, T>> {
487 BorrowMut::filter_map(self.try_borrow_mut()?, |object| {
488 (object as &mut dyn Any).downcast_mut::<T>()
489 })
490 .map_err(|_| match self.try_borrow() {
491 Ok(object) => ErrorKind::UnexpectedObjectType {
492 expected: T::type_static(),
493 unexpected: object.type_string(),
494 }
495 .into(),
496 Err(e) => e,
497 })
498 }
499
500 /// Returns true if the provided object occupies the same memory address
501 pub fn is_same_instance(&self, other: &Self) -> bool {
502 PtrMut::ptr_eq(&self.object, &other.object)
503 }
504
505 /// Returns the number of references currently held to the object
506 pub fn ref_count(&self) -> usize {
507 PtrMut::ref_count(&self.object)
508 }
509}
510
511impl<T: KotoObject> From<T> for KObject {
512 fn from(object: T) -> Self {
513 Self {
514 object: make_ptr_mut!(object),
515 }
516 }
517}
518
519impl fmt::Debug for KObject {
520 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
521 write!(f, "KObject ({:?})", PtrMut::address(&self.object))
522 }
523}
524
525/// A trait that represents the basic requirements of fields in a type that implements [`KotoObject`]
526///
527/// This is useful for reducing repetitive duplication in bounds when implementing a generic
528/// [KotoObject] type.
529pub trait KotoField: Clone + KotoSend + KotoSync + 'static {}
530impl<T> KotoField for T where T: Clone + KotoSend + KotoSync + 'static {}
531
532/// Context provided to a function that implements an object method
533///
534/// This is used by the `#[koto_impl]` macro when generating wrappers for functions tagged with
535/// `#[koto_method]`. A native function is called with a [CallContext], and for functions that
536/// implement object methods a [MethodContext] is produced when the first call argument is a
537/// [KObject].
538pub struct MethodContext<'a, T> {
539 /// The method call arguments
540 pub args: &'a [KValue],
541 /// A VM that can be used by the method for operations that require a runtime
542 //
543 // Q. Why isn't this a mutable reference like in CallContext?
544 // A. Because the arguments (including the object instance) have already been retrieved by
545 // reference from the VM, disallowing a mutable reference.
546 pub vm: &'a KotoVm,
547 // The instance of the object for the method call,
548 // accessible via the context's `instance`/`instance_mut` functions
549 object: &'a KObject,
550 // We want to be able to cast to `T`.
551 _phantom: PhantomData<T>,
552}
553
554impl<'a, T: KotoObject> MethodContext<'a, T> {
555 /// Makes a new method context
556 pub fn new(object: &'a KObject, args: &'a [KValue], vm: &'a KotoVm) -> Self {
557 Self {
558 object,
559 args,
560 vm,
561 _phantom: PhantomData,
562 }
563 }
564
565 /// Attempts to immutably borrow the object instance
566 pub fn instance(&self) -> Result<Borrow<'_, T>> {
567 self.object.cast::<T>()
568 }
569
570 /// Attempts to mutably borrow the object instance
571 pub fn instance_mut(&self) -> Result<BorrowMut<'_, T>> {
572 self.object.cast_mut::<T>()
573 }
574
575 /// Returns a clone of the instance as a [KValue]
576 ///
577 /// This is useful for builder methods.
578 /// e.g.
579 ///
580 /// ```koto
581 /// make_foo()
582 /// .set_x 99
583 /// .set_y 123
584 /// ```
585 ///
586 /// Here `set_x` and `set_y` would use `instance_result` to allow the builder chain to continue.
587 pub fn instance_result(&self) -> Result<KValue> {
588 Ok(self.object.clone().into())
589 }
590}
591
592/// Creates an error that describes an unimplemented method
593fn unimplemented_error<T>(fn_name: &'static str, object_type: KString) -> Result<T> {
594 runtime_error!(ErrorKind::Unimplemented {
595 fn_name,
596 object_type
597 })
598}
599
600/// An enum that indicates to the runtime if a [`KotoObject`] is iterable
601pub enum IsIterable {
602 /// The object is not iterable
603 NotIterable,
604 /// The object is iterable
605 ///
606 /// An iterable object is not itself an iterator, but provides an implementation of
607 /// [KotoObject::make_iterator] that is used to make an iterator when one is needed by the
608 /// runtime.
609 Iterable,
610 /// The object is a forward-only iterator
611 ///
612 /// A forward iterator provides an implementation of [KotoObject::iterator_next],
613 /// but not [KotoObject::iterator_next_back].
614 ForwardIterator,
615 /// The object is a bidirectional iterator.
616 ///
617 /// A bidirectional iterator provides an implementation of [KotoObject::iterator_next] and
618 /// [KotoObject::iterator_next_back].
619 BidirectionalIterator,
620}