Skip to content

Commit

Permalink
Adding support for arrays (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
elliotchance authored Jul 17, 2021
1 parent 4935eed commit 459c0f3
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 28 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ avoided.
- [Installation](#installation)
- [Values](#values)
- [Types](#types)
- [Arrays](#arrays)

Installation
------------
Expand Down Expand Up @@ -71,4 +72,24 @@ All `Values` have a `Type` which can be accessed on the `.typ` field.
- `.kind`: one of the `Kind` values: `is_bool`, `is_string`, `is_i8`, `is_i16`,
`is_int`, `is_i64`, `is_byte`, `is_u16`, `is_u32`, `is_u64`, `is_rune`,
`is_f32`, `is_f64`.
- `.elem`: Only applies for arrays, describes the element type.
- `.str()`: The string representation that matches the compile-time type in V.

Arrays
------

```v
import elliotchance.reflect
fn main() {
v := reflect.array_of([5, 6, 7])
println(v.typ) // "[]int"
println(v.typ.kind) // "array"
println(v.typ.elem.kind) // "int"
println(v.len()) // 3
println(v.cap()) // 3
println(v.get_index(1).get_int()) // 6
println(v.get_index(5)) // V panic: array index 5 is out of bounds (len = 3)
}
```
5 changes: 5 additions & 0 deletions kind.v
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module reflect

pub enum Kind {
// is_none is a special placeholder for cases where a type does not apply.
is_none
is_bool
is_string
is_i8
Expand All @@ -14,10 +16,12 @@ pub enum Kind {
is_rune
is_f32
is_f64
is_array
}

pub fn (k Kind) str() string {
return match k {
.is_none { '<none>' }
.is_bool { 'bool' }
.is_string { 'string' }
.is_i8 { 'i8' }
Expand All @@ -31,5 +35,6 @@ pub fn (k Kind) str() string {
.is_rune { 'rune' }
.is_f32 { 'f32' }
.is_f64 { 'f64' }
.is_array { 'array' }
}
}
32 changes: 30 additions & 2 deletions type.v
Original file line number Diff line number Diff line change
@@ -1,15 +1,42 @@
module reflect

pub struct Type {
pub:
kind Kind
// elem only applies for arrays.
elem &Type
}

pub fn (t Type) str() string {
return t.kind.str()
match t.kind {
.is_array {
return '[]' + (*t.elem).str()
}
else {
return t.kind.str()
}
}
}

// none_type is a special constructor to create a none type used in situations
// where the type is not applicable.
pub fn none_type() &Type {
return &Type{
kind: Kind.is_none
elem: 0
}
}

// parse_type returns the Type definition from the string representation.
pub fn parse_type(t string) ?Type {
if t.starts_with('[]') {
elem := parse_type(t[2..]) ?
return Type{
kind: Kind.is_array
elem: &elem
}
}

return Type{
kind: match t {
'bool' { Kind.is_bool }
Expand All @@ -25,7 +52,8 @@ pub fn parse_type(t string) ?Type {
'rune' { Kind.is_rune }
'f32' { Kind.is_f32 }
'f64' { Kind.is_f64 }
else { Kind.is_bool } // TODO(elliotchance): Fix me.
else { Kind.is_none }
}
elem: none_type()
}
}
35 changes: 22 additions & 13 deletions type_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,32 @@ module reflect
struct ParseTypeTest {
typ string
expected_kind Kind
expected_elem Type
}

fn test_parse_type() ? {
tests := [
ParseTypeTest{'bool', Kind.is_bool},
ParseTypeTest{'string', Kind.is_string},
ParseTypeTest{'i8', Kind.is_i8},
ParseTypeTest{'i16', Kind.is_i16},
ParseTypeTest{'int', Kind.is_int},
ParseTypeTest{'i64', Kind.is_i64},
ParseTypeTest{'byte', Kind.is_byte},
ParseTypeTest{'u16', Kind.is_u16},
ParseTypeTest{'u32', Kind.is_u32},
ParseTypeTest{'u64', Kind.is_u64},
ParseTypeTest{'rune', Kind.is_rune},
ParseTypeTest{'f32', Kind.is_f32},
ParseTypeTest{'f64', Kind.is_f64},
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()
}},
]
for test in tests {
typ := parse_type(test.typ) ?
Expand Down
86 changes: 73 additions & 13 deletions value.v
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ pub struct Value {
value_rune rune
value_f32 f32
value_f64 f64

array_len int
array_cap int
array_elem_size int
array voidptr
pub:
typ Type
}
Expand All @@ -24,98 +29,153 @@ pub:
pub fn value_of<T>(x T) Value {
$if T is bool {
return {
typ: Type{Kind.is_bool}
typ: Type{Kind.is_bool, none_type()}
value_bool: x
}
}

$if T is string {
return {
typ: Type{Kind.is_string}
typ: Type{Kind.is_string, none_type()}
value_string: x
}
}

$if T is i8 {
return {
typ: Type{Kind.is_i8}
typ: Type{Kind.is_i8, none_type()}
value_i8: x
}
}

$if T is i16 {
return {
typ: Type{Kind.is_i16}
typ: Type{Kind.is_i16, none_type()}
value_i16: x
}
}

$if T is int {
return {
typ: Type{Kind.is_int}
typ: Type{Kind.is_int, none_type()}
value_int: x
}
}

$if T is i64 {
return {
typ: Type{Kind.is_i64}
typ: Type{Kind.is_i64, none_type()}
value_i64: x
}
}

$if T is byte {
return {
typ: Type{Kind.is_byte}
typ: Type{Kind.is_byte, none_type()}
value_byte: x
}
}

$if T is u16 {
return {
typ: Type{Kind.is_u16}
typ: Type{Kind.is_u16, none_type()}
value_u16: x
}
}

$if T is u32 {
return {
typ: Type{Kind.is_u32}
typ: Type{Kind.is_u32, none_type()}
value_u32: x
}
}

$if T is u64 {
return {
typ: Type{Kind.is_u64}
typ: Type{Kind.is_u64, none_type()}
value_u64: x
}
}

$if T is rune {
return {
typ: Type{Kind.is_rune}
typ: Type{Kind.is_rune, none_type()}
value_rune: x
}
}

$if T is f32 {
return {
typ: Type{Kind.is_f32}
typ: Type{Kind.is_f32, none_type()}
value_f32: x
}
}

$if T is f64 {
return {
typ: Type{Kind.is_f64}
typ: Type{Kind.is_f64, none_type()}
value_f64: x
}
}

panic('unsupported value $x')
}

// array_of creates a Value from an array.
pub fn array_of<T>(x []T) Value {
return Value{
typ: parse_type(typeof(x).name) or { panic(err) }
array_len: x.len
array_cap: x.cap
array_elem_size: x.element_size
array: x.data
}
}

pub fn (v Value) len() int {
v.must_be(Kind.is_array)
return v.array_len
}

pub fn (v Value) cap() int {
v.must_be(Kind.is_array)
return v.array_cap
}

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)')
}

v2 := Value{
typ: *v.typ.elem
}
unsafe {
dest := match v2.typ.kind {
.is_bool { voidptr(&v2.value_bool) }
.is_string { voidptr(&v2.value_string) }
.is_i8 { voidptr(&v2.value_i8) }
.is_i16 { voidptr(&v2.value_i16) }
.is_int { voidptr(&v2.value_int) }
.is_i64 { voidptr(&v2.value_i64) }
.is_byte { voidptr(&v2.value_byte) }
.is_u16 { voidptr(&v2.value_u16) }
.is_u32 { voidptr(&v2.value_u32) }
.is_u64 { voidptr(&v2.value_u64) }
.is_rune { voidptr(&v2.value_rune) }
.is_f32 { voidptr(&v2.value_f32) }
.is_f64 { voidptr(&v2.value_f64) }
// TODO(elliotchance): Doesn't support multidimensional arrays.
else { voidptr(0) }
}

C.memcpy(dest, voidptr(u64(v.array) + u64(index * v.array_elem_size)), v.array_elem_size)
}
return v2
}

fn (v Value) must_be(k Kind) {
if v.typ.kind != k {
panic('value must be $k but is $v.typ.kind')
Expand Down
45 changes: 45 additions & 0 deletions value_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,48 @@ fn test_value_of_f64() {
assert v.typ.str() == 'f64'
assert v.get_f64() == 4.56
}

fn test_array_of_int() {
v := array_of([5, 7, 9])
assert v.typ.kind == Kind.is_array
assert v.typ.elem.kind == Kind.is_int
assert v.typ.str() == '[]int'
}

fn test_array_of_f64() {
v := array_of([1.23, 4.56, 7.89])
assert v.typ.kind == Kind.is_array
assert v.typ.elem.kind == Kind.is_f64
assert v.typ.str() == '[]f64'
}

fn test_len() {
v := array_of([5, 7, 9])
assert v.len() == 3
}

fn test_cap() {
v := array_of([]f32{len: 1, cap: 5})
assert v.len() == 1
assert v.cap() == 5
}

fn test_get_index_int() {
v := array_of([5, 7, 9])
e := v.get_index(1)
assert e.typ.str() == 'int'
assert e.get_int() == 7
}

fn test_get_index_f64() {
v := array_of([1.23, 4.56, 7.89])
e := v.get_index(2)
assert e.typ.str() == 'f64'
assert e.get_f64() == 7.89
}

// TODO(elliotchance): Not sure how to test for panics?
// fn test_get_index_bounds() {
// v := array_of([5, 7, 9])
// v.get_index(3)
// }

0 comments on commit 459c0f3

Please sign in to comment.