Skip to content

Commit

Permalink
add support for bitfields
Browse files Browse the repository at this point in the history
  • Loading branch information
oligon-fvs authored and dhubler committed Jan 19, 2024
1 parent fe20e07 commit d873266
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 10 deletions.
61 changes: 56 additions & 5 deletions node/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ func NewValue(typ *meta.Type, v interface{}) (val.Value, error) {
return toUnionList(typ, v)
case val.FmtLeafRef, val.FmtLeafRefList:
return NewValue(typ.Resolve(), v)
case val.FmtBits:
return toBits(typ.Bits(), v)
}
return val.Conv(typ.Format(), v)
}
Expand Down Expand Up @@ -116,7 +118,7 @@ func toIdentRefList(base []*meta.Identity, v interface{}) (val.IdentRefList, err
}
return refs, nil
}
return nil, fmt.Errorf("could not coerse '%v' into identref list", v)
return nil, fmt.Errorf("could not coerce '%v' into identref list", v)
}

func toEnumList(src val.EnumList, v interface{}) (val.EnumList, error) {
Expand Down Expand Up @@ -153,15 +155,18 @@ func toEnumList(src val.EnumList, v interface{}) (val.EnumList, error) {
return val.EnumList([]val.Enum{e}), nil
}
}
return nil, fmt.Errorf("could not coerse '%v' into enum list", v)
return nil, fmt.Errorf("could not coerce '%v' into enum list", v)
}

func toEnum(src val.EnumList, v interface{}) (val.Enum, error) {
id, isNum := val.Conv(val.FmtInt32, v)
if isNum == nil {
if id, isNum := val.Conv(val.FmtInt32, v); isNum == nil {
if e, found := src.ById(id.Value().(int)); found {
return e, nil
}
} else if id, isNum := val.Conv(val.FmtUInt32, v); isNum == nil {
if e, found := src.ById(int(id.Value().(uint))); found {
return e, nil
}
} else {
label, isLabel := val.Conv(val.FmtString, v)
if isLabel == nil {
Expand All @@ -170,7 +175,7 @@ func toEnum(src val.EnumList, v interface{}) (val.Enum, error) {
}
}
}
return val.Enum{}, fmt.Errorf("could not coerse '%v' into enum", v)
return val.Enum{}, fmt.Errorf("could not coerce '%v' into enum %v", v, src.String())
}

func toUnionList(typ *meta.Type, v interface{}) (val.Value, error) {
Expand All @@ -192,3 +197,49 @@ func toUnionList(typ *meta.Type, v interface{}) (val.Value, error) {
}
return nil, fmt.Errorf("could not coerce %v into UnionList", v)
}

func toBits(bitDefintions []*meta.Bit, v interface{}) (val.Bits, error) {
result := val.Bits{}
switch x := v.(type) {
case string: // treat string as list of bit identifiers separated by space
for _, strBit := range strings.Split(x, " ") {
for _, bitDef := range bitDefintions {
if strBit == bitDef.Ident() {
result.StringList = append(result.StringList, strBit)
result.Decimal = result.Decimal | (1 << bitDef.Position)
}
}
}
return result, nil
case float64: // default type for decimals from JSON parser
intVal := uint64(x)
// TODO: add verify that value has less or eq bits than definition
for _, bitDef := range bitDefintions {
if intVal&(1<<bitDef.Position) != 0 {
result.Decimal = result.Decimal | (1 << bitDef.Position)
result.StringList = append(result.StringList, bitDef.Ident())
}
}
return result, nil
case []string: // each string is bit identifier
for _, strBit := range x {
for _, bitDef := range bitDefintions {
if strBit == bitDef.Ident() {
result.StringList = append(result.StringList, strBit)
result.Decimal = result.Decimal | (1 << bitDef.Position)
}
}
}
return result, nil
case int:
for _, bitDef := range bitDefintions {
if x&(1<<bitDef.Position) != 0 {
result.Decimal = result.Decimal | (1 << bitDef.Position)
result.StringList = append(result.StringList, bitDef.Ident())
}
}
return result, nil
default:
return result, fmt.Errorf("could not coerce %v (of type %T) into UnionList", v, v)
}
}
34 changes: 34 additions & 0 deletions node/value_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,37 @@ func TestToUnionList(t *testing.T) {
fc.AssertEqual(t, nil, err)
fc.AssertEqual(t, []string{"thirty-two", "thirty-three"}, v.Value())
}

func TestToBits(t *testing.T) {
b := &meta.Builder{}
m := b.Module("x", nil)
l := b.Leaf(m, "l")
dt := b.Type(l, "bits")
b0 := b.Bit(dt, "b0")
b.Position(b0, 0)
b1 := b.Bit(dt, "b1")
b.Position(b1, 1)
fc.RequireEqual(t, nil, meta.Compile(m))

// cast from int (empty)
v, err := NewValue(dt, 0)
fc.AssertEqual(t, nil, err)
fc.AssertEqual(t, uint64(0), v.Value())
fc.AssertEqual(t, "", v.String())

// cast from int (non empty)
v, err = NewValue(dt, 0b11)
fc.AssertEqual(t, uint64(0b11), v.Value())
fc.AssertEqual(t, "b0 b1", v.String())

// cast from string
v, err = NewValue(dt, "b1")
fc.AssertEqual(t, uint64(0b10), v.Value())
fc.AssertEqual(t, "b1", v.String())

// cast from []string (wrong order)
v, err = NewValue(dt, []string{"b1", "b0"})
fc.AssertEqual(t, uint64(0b11), v.Value())
// side effect: keeping order so input and output data are equal
fc.AssertEqual(t, "b1 b0", v.String())
}
2 changes: 1 addition & 1 deletion nodeutil/json_wtr.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ func (wtr *JSONWtr) writeValue(p *node.Path, v val.Value) error {
if err := wtr.writeString(idtyStr); err != nil {
return err
}
case val.FmtString, val.FmtBinary:
case val.FmtString, val.FmtBinary, val.FmtBits:
if err := wtr.writeString(item.String()); err != nil {
return err
}
Expand Down
21 changes: 21 additions & 0 deletions nodeutil/reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -544,12 +544,16 @@ func (self Reflect) WriteFieldWithFieldName(fieldName string, m meta.Leafable, p
switch fieldVal.Kind() {
case reflect.String:
fieldVal.SetString(e.Label)
default:
return fmt.Errorf("cannot convert identityref value to fieldvalue '%v'. Please use 'string' for identityref field definition", fieldVal.Kind())
}
case val.FmtIdentityRefList:
el := v.(val.IdentRefList)
switch fieldVal.Elem().Kind() {
case reflect.String:
fieldVal.Set(reflect.ValueOf(el.Labels()))
default:
return fmt.Errorf("cannot convert identityref value to fieldvalue '%v'. Please use 'string' for identityref field definition", fieldVal.Kind())
}
case val.FmtEnum:
e := v.(val.Enum)
Expand All @@ -558,6 +562,8 @@ func (self Reflect) WriteFieldWithFieldName(fieldName string, m meta.Leafable, p
fieldVal.SetInt(int64(e.Id))
case reflect.String:
fieldVal.SetString(e.Label)
default:
return fmt.Errorf("cannot convert enum value to fieldvalue '%v'. Please use 'int' or 'string' for enum field definition", fieldVal.Kind())
}
case val.FmtEnumList:
el := v.(val.EnumList)
Expand All @@ -566,6 +572,21 @@ func (self Reflect) WriteFieldWithFieldName(fieldName string, m meta.Leafable, p
fieldVal.Set(reflect.ValueOf(el.Ids()))
case reflect.String:
fieldVal.Set(reflect.ValueOf(el.Labels()))
default:
return fmt.Errorf("cannot convert enum value to fieldvalue '%v'. Please use 'int' or 'string' for enum field definition", fieldVal.Kind())
}
case val.FmtBits:
b := v.(val.Bits)
switch fieldVal.Kind() {
case reflect.Slice:
if fieldVal.Type().Elem().Kind() != reflect.String {
return fmt.Errorf("cannot assign bits value to type %T, only '[]string' or 'int' are accepted for bits representation", fieldVal.Interface())
}
fieldVal.Set(reflect.ValueOf(b.StringList))
case reflect.Int:
fieldVal.SetInt(int64(b.Decimal))
default:
return fmt.Errorf("cannot convert bits value to fieldvalue '%v'. Please use 'int' or '[]string' for bits field definition", fieldVal.Kind())
}
default:
value := reflect.ValueOf(v.Value())
Expand Down
11 changes: 7 additions & 4 deletions val/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -865,18 +865,21 @@ func (NotEmptyType) Value() interface{} {

//////////////////////////

type Bits []byte
type Bits struct {
Decimal uint64
StringList []string
}

func (Bits) Format() Format {
return FmtEmpty
return FmtBits
}

func (b Bits) String() string {
return string(b)
return strings.Join(b.StringList, " ")
}

func (b Bits) Value() interface{} {
return []byte(b)
return b.Decimal
}

type BitsList [][]byte
Expand Down

0 comments on commit d873266

Please sign in to comment.