From 277c379554dd7be59e3af70bf144353aa867a915 Mon Sep 17 00:00:00 2001 From: Elliot Chance Date: Fri, 23 Jul 2021 23:00:28 -0400 Subject: [PATCH] Adding support for maps (#5) Works with any key or value type and provides access to these types. Added keys() and get_key() respectively. --- README.md | 27 +++++++ kind.v | 2 + type.v | 23 +++++- type_test.v | 42 +++++------ value.v | 204 ++++++++++++++++++++++++++++++++++++++++++--------- value_test.v | 65 +++++++++++++++- 6 files changed, 303 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 34aada9..1aefa91 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ avoided. - [Values](#values) - [Types](#types) - [Arrays](#arrays) +- [Maps](#maps) Installation ------------ @@ -93,3 +94,29 @@ fn main() { println(v.get_index(5)) // V panic: array index 5 is out of bounds (len = 3) } ``` + +Maps +---- + +```v +import elliotchance.reflect + +fn main() { + mut m := map[string]int{} + m['a'] = 5 + m['b'] = 7 + m['c'] = 9 + + v := reflect.map_of(m) + + println(v.typ) // "map[string]int" + println(v.typ.kind) // "map" + println(v.typ.key.kind) // "string" + println(v.typ.elem.kind) // "int" + println(v.len()) // 3 + println(v.keys()) // ['a', 'b', 'c'] + println(v.get_key(reflect.value_of('b')).get_int()) // 7 + println(v.get_key(reflect.value_of('d'))) // V panic: key not found: d + println(v.get_key(reflect.value_of(123))) // V panic: value must be string but is int +} +``` diff --git a/kind.v b/kind.v index 88a8eba..9109f0c 100644 --- a/kind.v +++ b/kind.v @@ -17,6 +17,7 @@ pub enum Kind { is_f32 is_f64 is_array + is_map } pub fn (k Kind) str() string { @@ -36,5 +37,6 @@ pub fn (k Kind) str() string { .is_f32 { 'f32' } .is_f64 { 'f64' } .is_array { 'array' } + .is_map { 'map' } } } diff --git a/type.v b/type.v index 4af1e02..1eee773 100644 --- a/type.v +++ b/type.v @@ -3,14 +3,19 @@ module reflect pub struct Type { pub: kind Kind - // elem only applies for arrays. + // elem is the element type for arrays and the value type for maps. elem &Type + // key is only used for describing the map key type. + key &Type } pub fn (t Type) str() string { match t.kind { .is_array { - return '[]' + (*t.elem).str() + return '[]${*t.elem}' + } + .is_map { + return 'map[${*t.key}]${*t.elem}' } else { return t.kind.str() @@ -24,6 +29,7 @@ pub fn none_type() &Type { return &Type{ kind: Kind.is_none elem: 0 + key: 0 } } @@ -34,6 +40,18 @@ pub fn parse_type(t string) ?Type { return Type{ kind: Kind.is_array elem: &elem + key: none_type() + } + } + + if t.starts_with('map[') { + parts := t[4..].split(']') + key := parse_type(parts[0]) ? + elem := parse_type(parts[1]) ? + return Type{ + kind: Kind.is_map + elem: &elem + key: &key } } @@ -55,5 +73,6 @@ pub fn parse_type(t string) ?Type { else { Kind.is_none } } elem: none_type() + key: none_type() } } diff --git a/type_test.v b/type_test.v index 36d7ad7..3c1cabb 100644 --- a/type_test.v +++ b/type_test.v @@ -4,35 +4,35 @@ struct ParseTypeTest { typ string expected_kind Kind expected_elem Type + expected_key Type } fn test_parse_type() ? { tests := [ - ParseTypeTest{'bool', Kind.is_bool, none_type()}, - ParseTypeTest{'string', Kind.is_string, none_type()}, - ParseTypeTest{'i8', Kind.is_i8, none_type()}, - ParseTypeTest{'i16', Kind.is_i16, none_type()}, - ParseTypeTest{'int', Kind.is_int, none_type()}, - ParseTypeTest{'i64', Kind.is_i64, none_type()}, - ParseTypeTest{'byte', Kind.is_byte, none_type()}, - ParseTypeTest{'u16', Kind.is_u16, none_type()}, - ParseTypeTest{'u32', Kind.is_u32, none_type()}, - ParseTypeTest{'u64', Kind.is_u64, none_type()}, - ParseTypeTest{'rune', Kind.is_rune, none_type()}, - ParseTypeTest{'f32', Kind.is_f32, none_type()}, - ParseTypeTest{'f64', Kind.is_f64, none_type()}, - ParseTypeTest{'[]int', Kind.is_array, &Type{ - kind: Kind.is_int - elem: none_type() - }}, - ParseTypeTest{'[]f64', Kind.is_array, &Type{ - kind: Kind.is_f64 - elem: none_type() - }}, + ParseTypeTest{'bool', Kind.is_bool, none_type(), none_type()}, + ParseTypeTest{'string', Kind.is_string, none_type(), none_type()}, + ParseTypeTest{'i8', Kind.is_i8, none_type(), none_type()}, + ParseTypeTest{'i16', Kind.is_i16, none_type(), none_type()}, + ParseTypeTest{'int', Kind.is_int, none_type(), none_type()}, + ParseTypeTest{'i64', Kind.is_i64, none_type(), none_type()}, + ParseTypeTest{'byte', Kind.is_byte, none_type(), none_type()}, + ParseTypeTest{'u16', Kind.is_u16, none_type(), none_type()}, + ParseTypeTest{'u32', Kind.is_u32, none_type(), none_type()}, + ParseTypeTest{'u64', Kind.is_u64, none_type(), none_type()}, + ParseTypeTest{'rune', Kind.is_rune, none_type(), none_type()}, + ParseTypeTest{'f32', Kind.is_f32, none_type(), none_type()}, + ParseTypeTest{'f64', Kind.is_f64, none_type(), none_type()}, + ParseTypeTest{'[]int', Kind.is_array, &Type{Kind.is_int, none_type(), none_type()}, none_type()}, + ParseTypeTest{'[]f64', Kind.is_array, &Type{Kind.is_f64, none_type(), none_type()}, none_type()}, + ParseTypeTest{'map[string]int', Kind.is_map, &Type{Kind.is_int, none_type(), none_type()}, &Type{Kind.is_string, none_type(), none_type()}}, + ParseTypeTest{'map[f64]string', Kind.is_map, &Type{Kind.is_string, none_type(), none_type()}, &Type{Kind.is_f64, none_type(), none_type()}}, ] for test in tests { + println(test.typ) typ := parse_type(test.typ) ? assert typ.kind == test.expected_kind + assert (*typ.elem).str() == test.expected_elem.str() + assert (*typ.key).str() == test.expected_key.str() assert typ.str() == test.typ } } diff --git a/value.v b/value.v index 6f65385..472cb68 100644 --- a/value.v +++ b/value.v @@ -17,10 +17,15 @@ pub struct Value { value_f32 f32 value_f64 f64 - array_len int + array_or_map_len int + array_cap int array_elem_size int - array voidptr + + map_keys []Value + map_values []Value + // pointer to the array or map + obj voidptr pub: typ Type } @@ -28,92 +33,92 @@ pub: // value_of is used to create a Value pub fn value_of(x T) Value { $if T is bool { - return { - typ: Type{Kind.is_bool, none_type()} + return Value{ + typ: Type{Kind.is_bool, none_type(), none_type()} value_bool: x } } $if T is string { - return { - typ: Type{Kind.is_string, none_type()} + return Value{ + typ: Type{Kind.is_string, none_type(), none_type()} value_string: x } } $if T is i8 { - return { - typ: Type{Kind.is_i8, none_type()} + return Value{ + typ: Type{Kind.is_i8, none_type(), none_type()} value_i8: x } } $if T is i16 { - return { - typ: Type{Kind.is_i16, none_type()} + return Value{ + typ: Type{Kind.is_i16, none_type(), none_type()} value_i16: x } } $if T is int { - return { - typ: Type{Kind.is_int, none_type()} + return Value{ + typ: Type{Kind.is_int, none_type(), none_type()} value_int: x } } $if T is i64 { - return { - typ: Type{Kind.is_i64, none_type()} + return Value{ + typ: Type{Kind.is_i64, none_type(), none_type()} value_i64: x } } $if T is byte { - return { - typ: Type{Kind.is_byte, none_type()} + return Value{ + typ: Type{Kind.is_byte, none_type(), none_type()} value_byte: x } } $if T is u16 { - return { - typ: Type{Kind.is_u16, none_type()} + return Value{ + typ: Type{Kind.is_u16, none_type(), none_type()} value_u16: x } } $if T is u32 { - return { - typ: Type{Kind.is_u32, none_type()} + return Value{ + typ: Type{Kind.is_u32, none_type(), none_type()} value_u32: x } } $if T is u64 { - return { - typ: Type{Kind.is_u64, none_type()} + return Value{ + typ: Type{Kind.is_u64, none_type(), none_type()} value_u64: x } } $if T is rune { - return { - typ: Type{Kind.is_rune, none_type()} + return Value{ + typ: Type{Kind.is_rune, none_type(), none_type()} value_rune: x } } $if T is f32 { - return { - typ: Type{Kind.is_f32, none_type()} + return Value{ + typ: Type{Kind.is_f32, none_type(), none_type()} value_f32: x } } $if T is f64 { - return { - typ: Type{Kind.is_f64, none_type()} + return Value{ + typ: Type{Kind.is_f64, none_type(), none_type()} value_f64: x } } @@ -125,16 +130,34 @@ pub fn value_of(x T) Value { pub fn array_of(x []T) Value { return Value{ typ: parse_type(typeof(x).name) or { panic(err) } - array_len: x.len + array_or_map_len: x.len array_cap: x.cap array_elem_size: x.element_size - array: x.data + obj: x.data + } +} + +// map_of creates a Value from a map. +pub fn map_of(x map[K]V) Value { + mut keys := []Value{cap: x.len} + mut values := []Value{cap: x.len} + + for k, v in x { + keys << value_of(k) + values << value_of(v) + } + + return Value{ + typ: parse_type(typeof(x).name) or { panic(err) } + array_or_map_len: x.len + map_keys: keys + map_values: values } } pub fn (v Value) len() int { - v.must_be(Kind.is_array) - return v.array_len + v.must_be2(Kind.is_array, Kind.is_map) + return v.array_or_map_len } pub fn (v Value) cap() int { @@ -145,8 +168,8 @@ pub fn (v Value) cap() int { pub fn (v Value) get_index(index int) Value { v.must_be(Kind.is_array) - if v.array_len < 0 || v.array_len <= index { - panic('array index $index is out of bounds (len = $v.array_len)') + if v.array_or_map_len < 0 || v.array_or_map_len <= index { + panic('array index $index is out of bounds (len = $v.array_or_map_len)') } v2 := Value{ @@ -171,7 +194,7 @@ pub fn (v Value) get_index(index int) Value { else { voidptr(0) } } - C.memcpy(dest, voidptr(u64(v.array) + u64(index * v.array_elem_size)), v.array_elem_size) + C.memcpy(dest, voidptr(u64(v.obj) + u64(index * v.array_elem_size)), v.array_elem_size) } return v2 } @@ -182,6 +205,12 @@ fn (v Value) must_be(k Kind) { } } +fn (v Value) must_be2(k1 Kind, k2 Kind) { + if v.typ.kind != k1 && v.typ.kind != k2 { + panic('value must be $k1 or $k2 but is $v.typ.kind') + } +} + pub fn (v Value) get_bool() bool { v.must_be(Kind.is_bool) return v.value_bool @@ -246,3 +275,108 @@ pub fn (v Value) get_f64() f64 { v.must_be(Kind.is_f64) return v.value_f64 } + +pub fn (v Value) get_key(key Value) Value { + v.must_be(Kind.is_map) + key.must_be(v.typ.key.kind) + + for i, k in v.map_keys { + if k.eq(key) { + return Value{ + typ: *v.typ.elem + value_bool: v.map_values[i].value_bool + value_string: v.map_values[i].value_string + value_i8: v.map_values[i].value_i8 + value_i16: v.map_values[i].value_i16 + value_int: v.map_values[i].value_int + value_i64: v.map_values[i].value_i64 + value_byte: v.map_values[i].value_byte + value_u16: v.map_values[i].value_u16 + value_u32: v.map_values[i].value_u32 + value_u64: v.map_values[i].value_u64 + value_rune: v.map_values[i].value_rune + value_f32: v.map_values[i].value_f32 + value_f64: v.map_values[i].value_f64 + } + } + } + + panic('key not found: $key') +} + +fn (v Value) eq(v2 Value) bool { + if v.typ.kind != v2.typ.kind { + return false + } + + return match v.typ.kind { + .is_bool { + v.value_bool == v2.value_bool + } + .is_string { + v.value_string == v2.value_string + } + .is_i8 { + v.value_i8 == v2.value_i8 + } + .is_i16 { + v.value_i16 == v2.value_i16 + } + .is_int { + v.value_int == v2.value_int + } + .is_i64 { + v.value_i64 == v2.value_i64 + } + .is_byte { + v.value_byte == v2.value_byte + } + .is_u16 { + v.value_u16 == v2.value_u16 + } + .is_u32 { + v.value_u32 == v2.value_u32 + } + .is_u64 { + v.value_u64 == v2.value_u64 + } + .is_rune { + v.value_rune == v2.value_rune + } + .is_f32 { + v.value_f32 == v2.value_f32 + } + .is_f64 { + v.value_f64 == v2.value_f64 + } + else { + panic('cannot compare $v.str() and $v2.str()') + false + } + } +} + +fn (v Value) str() string { + return match v.typ.kind { + .is_bool { '$v.value_bool' } + .is_string { '$v.value_string' } + .is_i8 { '$v.value_i8' } + .is_i16 { '$v.value_i16' } + .is_int { '$v.value_int' } + .is_i64 { '$v.value_i64' } + .is_byte { '$v.value_byte' } + .is_u16 { '$v.value_u16' } + .is_u32 { '$v.value_u32' } + .is_u64 { '$v.value_u64' } + .is_rune { '$v.value_rune' } + .is_f32 { '$v.value_f32' } + .is_f64 { '$v.value_f64' } + // TODO(elliotchance): We should print arrays and maps + else { '' } + } +} + +pub fn (v Value) keys() []Value { + v.must_be(Kind.is_map) + return v.map_keys +} diff --git a/value_test.v b/value_test.v index a0298ca..54d2c9c 100644 --- a/value_test.v +++ b/value_test.v @@ -105,12 +105,12 @@ fn test_array_of_f64() { assert v.typ.str() == '[]f64' } -fn test_len() { +fn test_array_of_len() { v := array_of([5, 7, 9]) assert v.len() == 3 } -fn test_cap() { +fn test_array_of_cap() { v := array_of([]f32{len: 1, cap: 5}) assert v.len() == 1 assert v.cap() == 5 @@ -135,3 +135,64 @@ fn test_get_index_f64() { // v := array_of([5, 7, 9]) // v.get_index(3) // } + +fn test_map_of_string_int() { + mut m := map[string]int{} + m['a'] = 5 + m['b'] = 7 + m['c'] = 9 + v := map_of(m) + assert v.typ.kind == Kind.is_map + assert v.typ.key.kind == Kind.is_string + assert v.typ.elem.kind == Kind.is_int + assert v.typ.str() == 'map[string]int' + assert v.len() == 3 +} + +fn test_get_key_int() { + mut m := map[string]int{} + m['a'] = 5 + m['b'] = 7 + m['c'] = 9 + v := map_of(m) + + e := v.get_key(value_of('b')) + assert e.typ.str() == 'int' + assert e.get_int() == 7 +} + +// TODO(elliotchance): Not sure how to test for panics? +// fn test_get_key_bounds() { +// mut m := map[string]int{} +// m['a'] = 5 +// m['b'] = 7 +// m['c'] = 9 +// v := map_of(m) + +// v.get_key(value_of('d')) +// } + +// TODO(elliotchance): Not sure how to test for panics? +// fn test_get_key_type() { +// mut m := map[string]int{} +// m['a'] = 5 +// m['b'] = 7 +// m['c'] = 9 +// v := map_of(m) + +// v.get_key(value_of(123)) +// } + +fn test_map_keys() { + mut m := map[string]int{} + m['a'] = 5 + m['b'] = 7 + m['c'] = 9 + v := map_of(m) + + keys := v.keys() + assert keys.len == 3 + + expected := [keys[0].get_string(), keys[1].get_string(), keys[2].get_string()] + assert expected == ['a', 'b', 'c'] +}