Skip to content

Commit 3d576ae

Browse files
authored
Merge pull request #361 from koto-lang/koto-object-index-mut
Mutable indexing improvements
2 parents b6a9a1e + 3f17ee8 commit 3d576ae

File tree

12 files changed

+167
-30
lines changed

12 files changed

+167
-30
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ The Koto project adheres to
1717
- `export` can be used with multi-assignment expressions.
1818
- E.g. expressions like `export a, b, c = foo()` are now allowed.
1919
- Maps now support `[]` indexing, returning the Nth entry as a tuple.
20+
- Entries can also be replaced by index by assigning a key/value tuple.
2021
- Objects that implement `KotoObject::call` can now be used in operations that
2122
expect functions.
2223
- `KotoObject::is_callable` has been added to support this, and needs to be
@@ -33,6 +34,8 @@ The Koto project adheres to
3334
- The macros in `koto_derive` have been updated to support generics.
3435
- `KotoField` has been added to reduce boilerplate when using the derive
3536
macros.
37+
- `KotoObject::index_mut` has been added to allow objects to support mutable
38+
indexing operations.
3639
- `TryFrom<KValue>` has been implemented for some `core` and `std` types,
3740
including `bool`, string, and number types.
3841

crates/bytecode/src/compiler.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2680,7 +2680,7 @@ impl Compiler {
26802680
}
26812681
ChainNode::Index(_) => {
26822682
let index = index.unwrap(self)?;
2683-
self.push_op(SetIndex, &[container_register, index, value_register]);
2683+
self.push_op(IndexMut, &[container_register, index, value_register]);
26842684
}
26852685
_ => {}
26862686
}

crates/bytecode/src/instruction.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ pub enum Instruction {
270270
value: u8,
271271
index: u8,
272272
},
273-
SetIndex {
273+
IndexMut {
274274
register: u8,
275275
index: u8,
276276
value: u8,
@@ -693,13 +693,13 @@ impl fmt::Debug for Instruction {
693693
f,
694694
"Index\t\tresult: {register}\tvalue: {value}\tindex: {index}"
695695
),
696-
SetIndex {
696+
IndexMut {
697697
register,
698698
index,
699699
value,
700700
} => write!(
701701
f,
702-
"SetIndex\tregister: {register}\tindex: {index}\tvalue: {value}"
702+
"IndexMut\tregister: {register}\tindex: {index}\tvalue: {value}"
703703
),
704704
MapInsert {
705705
register,

crates/bytecode/src/instruction_reader.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ impl Iterator for InstructionReader {
386386
value: get_u8!(),
387387
index: get_u8!(),
388388
}),
389-
Op::SetIndex => Some(SetIndex {
389+
Op::IndexMut => Some(IndexMut {
390390
register: get_u8!(),
391391
index: get_u8!(),
392392
value: get_u8!(),

crates/bytecode/src/op.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ pub enum Op {
413413
/// Sets a contained value via index
414414
///
415415
/// `[*indexable, *value, *index]`
416-
SetIndex,
416+
IndexMut,
417417

418418
/// Inserts a key/value entry into a map
419419
///

crates/cli/docs/language_guide.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,15 @@ print! m[1]
643643
check! ('oranges', 99)
644644
```
645645

646+
Entries can also be replaced by assigning a key/value tuple to the entry's index.
647+
648+
```koto
649+
m = {apples: 42, oranges: 99, lemons: 63}
650+
m[1] = ('pears', 123)
651+
print! m
652+
check! {apples: 42, pears: 123, lemons: 63}
653+
```
654+
646655
### Shorthand Values
647656

648657
Koto supports a shorthand notation when creating maps with inline syntax.

crates/cli/docs/libs/color.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,15 +214,18 @@ check! Color(RGB, r: 0.2, g: 0.4, b: 0.3, a: 0.5)
214214

215215
The `color` module's core color type.
216216

217+
An `alpha` value is always present as the color's fourth component.
218+
217219
The color may belong to various different color spaces,
218220
with the space's components available via iteration or indexing.
219221

220-
An `alpha` value is always present as the fourth component.
222+
Components can be modified via index.
223+
221224

222225
### Example
223226

224227
```koto
225-
r, g, b = color('yellow')
228+
r, g, b = color 'yellow'
226229
print! r, g, b
227230
check! (1.0, 1.0, 0.0)
228231
@@ -232,6 +235,13 @@ check! (90.0, 0.5, 0.25, 1.0)
232235
233236
print! color('red')[0]
234237
check! 1.0
238+
239+
print! c = color.oklch 0.5, 0.1, 180
240+
check! Color(Oklch, l: 0.5, c: 0.1, h: 180, a: 1)
241+
c[0] = 0.25 # Set the lightness component to 0.25
242+
c[3] = 0.1 # Set the alpha component to 0.1
243+
print c
244+
check! Color(Oklch, l: 0.25, c: 0.1, h: 180, a: 0.1)
235245
```
236246

237247
## Color.mix

crates/runtime/src/types/object.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ pub trait KotoObject: KotoType + KotoCopy + KotoEntries + KotoSend + KotoSync +
116116
///
117117
/// By default, the object's type is used as the display string.
118118
///
119-
/// The [`DisplayContext`] is used to append strings to the result, and also provides context
119+
/// The [`DisplayContext`] is used to append strings to the result, and provides information
120120
/// about any parent containers.
121121
fn display(&self, ctx: &mut DisplayContext) -> Result<()> {
122122
ctx.append(self.type_string());
@@ -130,6 +130,13 @@ pub trait KotoObject: KotoType + KotoCopy + KotoEntries + KotoSend + KotoSync +
130130
unimplemented_error("@index", self.type_string())
131131
}
132132

133+
/// Called when mutating an object via indexing, e.g. `x[0] = 99`
134+
///
135+
/// See also: [KotoObject::size]
136+
fn index_mut(&mut self, _index: &KValue, _value: &KValue) -> Result<()> {
137+
unimplemented_error("@index_mut", self.type_string())
138+
}
139+
133140
/// Called when checking for the number of elements contained in the object
134141
///
135142
/// The size should represent the maximum valid index that can be passed to

crates/runtime/src/vm.rs

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -892,11 +892,11 @@ impl KotoVm {
892892
value,
893893
index,
894894
} => self.run_index(register, value, index)?,
895-
SetIndex {
895+
IndexMut {
896896
register,
897897
index,
898898
value,
899-
} => self.run_set_index(register, index, value)?,
899+
} => self.run_index_mut(register, index, value)?,
900900
MapInsert {
901901
register,
902902
key,
@@ -2221,7 +2221,7 @@ impl KotoVm {
22212221
import_result
22222222
}
22232223

2224-
fn run_set_index(
2224+
fn run_index_mut(
22252225
&mut self,
22262226
indexable_register: u8,
22272227
index_register: u8,
@@ -2230,8 +2230,8 @@ impl KotoVm {
22302230
use KValue::*;
22312231

22322232
let indexable = self.clone_register(indexable_register);
2233-
let index_value = self.clone_register(index_register);
2234-
let value = self.clone_register(value_register);
2233+
let index_value = self.get_register(index_register);
2234+
let value = self.get_register(value_register);
22352235

22362236
match indexable {
22372237
List(list) => {
@@ -2240,24 +2240,50 @@ impl KotoVm {
22402240
match index_value {
22412241
Number(index) => {
22422242
let u_index = usize::from(index);
2243-
if index >= 0.0 && u_index < list_len {
2244-
list_data[u_index] = value;
2243+
if *index >= 0.0 && u_index < list_len {
2244+
list_data[u_index] = value.clone();
22452245
} else {
2246-
return runtime_error!("Index '{index}' not in List");
2246+
return runtime_error!("Invalid index ({index})");
22472247
}
22482248
}
22492249
Range(range) => {
22502250
for i in range.indices(list_len) {
22512251
list_data[i] = value.clone();
22522252
}
22532253
}
2254-
unexpected => return unexpected_type("index", &unexpected),
2254+
unexpected => return unexpected_type("Number or Range", unexpected),
22552255
}
2256+
Ok(())
22562257
}
2257-
unexpected => return unexpected_type("a mutable indexable value", &unexpected),
2258-
};
2259-
2260-
Ok(())
2258+
Map(map) => match index_value {
2259+
Number(index) => {
2260+
let mut map_data = map.data_mut();
2261+
let map_len = map_data.len();
2262+
let u_index = usize::from(index);
2263+
if *index >= 0.0 && u_index < map_len {
2264+
match value {
2265+
Tuple(new_entry) if new_entry.len() == 2 => {
2266+
let key = ValueKey::try_from(new_entry[0].clone())?;
2267+
// There's no API on IndexMap for replacing an entry,
2268+
// so use swap_remove_index to remove the old entry,
2269+
// then insert the new entry at the end of the map,
2270+
// followed by swap_indices to swap the new entry back into position.
2271+
map_data.swap_remove_index(u_index);
2272+
map_data.insert(key, new_entry[1].clone());
2273+
map_data.swap_indices(u_index, map_len - 1);
2274+
Ok(())
2275+
}
2276+
unexpected => unexpected_type("Tuple with 2 elements", unexpected),
2277+
}
2278+
} else {
2279+
runtime_error!("Invalid index ({index})")
2280+
}
2281+
}
2282+
unexpected => unexpected_type("Number", unexpected),
2283+
},
2284+
Object(o) => o.try_borrow_mut()?.index_mut(index_value, value),
2285+
unexpected => unexpected_type("a mutable indexable value", &unexpected),
2286+
}
22612287
}
22622288

22632289
fn validate_index(&self, n: KNumber, size: Option<usize>) -> Result<usize> {

crates/runtime/tests/object_tests.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,22 @@ mod objects {
140140
}
141141
}
142142

143+
fn index_mut(&mut self, index: &KValue, value: &KValue) -> Result<()> {
144+
match index {
145+
KValue::Number(index) => {
146+
assert_eq!(usize::from(index), 0);
147+
match value {
148+
KValue::Number(value) => {
149+
self.x = value.into();
150+
Ok(())
151+
}
152+
unexpected => unexpected_type("Number as value", unexpected),
153+
}
154+
}
155+
unexpected => unexpected_type("Number as index", unexpected),
156+
}
157+
}
158+
143159
fn size(&self) -> Option<usize> {
144160
Some(self.x.unsigned_abs() as usize)
145161
}
@@ -749,6 +765,24 @@ match make_object 10
749765
";
750766
test_object_script(script, 45);
751767
}
768+
769+
#[test]
770+
fn index_mut_assign() {
771+
let script = "
772+
x = make_object 100
773+
x[0] = 23
774+
";
775+
test_object_script(script, 23);
776+
}
777+
778+
#[test]
779+
fn index_mut_compound_assign() {
780+
let script = "
781+
x = make_object 100
782+
x[0] += 1
783+
";
784+
test_object_script(script, 101);
785+
}
752786
}
753787

754788
#[test]

0 commit comments

Comments
 (0)