Reflection
Ki has some basic, but powerful support for reflection.
Basics
Reflection is accessed using :
.
Types, sizes and bytes
struct Person {
var name: string
var age: u8
}
fn main() {
var charlie: Person("Charlie", 33)
echo("charlie is a {{charlie:type}}")
echo("A Person is {{Person:size}} bytes")
echo("charlie is {{charlie:type:size}} bytes")
# `:bytes` gets an array of an instance's bytes
var bytes: charlie:bytes
for (n, byte in bytes) {
echo("Byte {{n + 1}}: 0x{{hex(byte)}}")
}
}
This program will print (on a 64-bit system):
charlie is a Person
A Person is 9 bytes
charlie is 9 bytes
Byte 1: 0xed
Byte 2: 0xe0
Byte 3: 0x20
Byte 4: 0x95
Byte 5: 0xb1
Byte 6: 0x06
Byte 7: 0x45
Byte 8: 0xc2
Byte 9: 0x21
More type information
In a type
, the base can be accessed using :base
:
type PersonCount(uint) {
fn announce() {
echo("I have counted {{:base}} people")
}
fn has_enough(needed: uint): (bool) {
return(:base >= needed)
}
}
Operator overloading
Ki uses reflection to implement operator overloading:
struct PersonStruct {
var name: string
var age: u8
}
type Person(PersonStruct) {
fn :eq(p: &Person) {
return(self.name == p.name && self.age == p.age)
}
fn :gt(p: &Person) {
return(self.age > p.age)
}
fn :lt(p: &Person) {
return(self.age < p.age)
}
fn :add(p: &Person): (Person) {
return(Person("{{self.name and {{p.name}}", self.age + p.age))
}
}
Overloadable operators
Containers
:iterate
andnext
: Used withfor
-in
loops:in
: Used forin
checks:getitem
and:setitem
: Used with[]
Misc
:string
: Returns a string representation:hash
: Returns an integer suitable for hashing:enter
and:exit
: Creates a context for use withwith
Comparisons
:eq
,:ne
,:lt
,:lte
,:gt
,:gte
:==, !=, <, <=, >, >=
:eq
:==
:is
:===
:ne
:!=
:isnot
:!==
:lt
:<
:lte
:<=
:gt
:>
:gte
:>=
Like Python, the only related comparison operators are :eq
and :ne
. For
example, :gte
won't use :eq
and :gt
even if you override them; you have
to override :gte
specifically.
Math
:add
:+
:sub
:-
:mul
:*
:matmul
:@
:div
:/
:rem
:%
:exp
:**
:and
:&
:or
:|
:xor
:^
:lshift
:<<
:rshift
:>>
:not
:~
(unary):neg
:-
(unary):pos
:+
(unary)
Constants
Reflection can be used when defining constants.
range DayOfWeek [1..7]
struct WeekDayStruct {
var name: string
var number: DayOfWeek
}
type WeekDay(WeekDayStruct) {
pred is_weekend_day {
return(self.number == 1 || self.number == 7)
}
}
const WeekDays(WeekDay) {
Sunday(:name, 1)
Monday(:name, 2)
Tuesday(:name, 3)
Wednesday(:name, 4)
Thursday(:name, 5)
Friday(:name, 6)
Saturday(:name, 7)
}
If you wanted, you could use :count
instead of integer literals in
WeekDays
:
const WeekDays(WeekDay) {
Sunday(:name, :count)
Monday(:name, :count)
Tuesday(:name, :count)
Wednesday(:name, :count)
Thursday(:name, :count)
Friday(:name, :count)
Saturday(:name, :count)
}
In this instance it's a little less accurate however. A WeekDay
's ordinal
shouldn't change depending on its ordering in WeekDays
; it should be
constant. A better example might be:
struct Weapon {
var name: string
var slot: uint
}
const Weapons(Weapon) {
Fist(:name, :count)
Chainsaw(:name, :count)
Pistol(:name, :count)
Shotgun(:name, :count)
SuperShotgun(:name, :count)
Chaingun(:name, :count)
RocketLauncher(:name, :count)
PlasmaRifle(:name, :count)
BFG(:name, :count)
}
A Weapon
's slot doesn't particularly matter; it just needs an index. const
can provide that easily.