Compare commits

...

6 Commits

Author SHA1 Message Date
Alejandro Mery c94e5e74ad reflection: WIP 1 year ago
Alejandro Mery dd252fafae reflection: add initial New() validating type and triggering a scan 1 year ago
Alejandro Mery f68272eb84 reflection: introduce UnmarshalTypeError{} 1 year ago
Alejandro Mery 1e95acf21d reflection: introduce InvalidUnmarshalError{} 1 year ago
Alejandro Mery 2b5dcec64d lexer: add Hint to Error, which is expanded as "%s" instead of "%q" 1 year ago
Alejandro Mery 4aa6233e4f lexer: refactor Error.Error()'s prefix generator 1 year ago
  1. 8
      go.mod
  2. 26
      lexer/error.go
  3. 69
      reflection/error.go
  4. 16
      reflection/reflect_help.go
  5. 40
      reflection/reflection.go
  6. 37
      reflection/scan.go
  7. 108
      reflection/typeinfo.go

8
go.mod

@ -2,20 +2,22 @@ module asciigoat.org/core
go 1.19 go 1.19
require github.com/mgechev/revive v1.3.3 require (
github.com/fatih/structtag v1.2.0
github.com/mgechev/revive v1.3.3
github.com/pkg/errors v0.9.1
)
require ( require (
github.com/BurntSushi/toml v1.3.2 // indirect github.com/BurntSushi/toml v1.3.2 // indirect
github.com/chavacava/garif v0.0.0-20230608123814-4bd63c2919ab // indirect github.com/chavacava/garif v0.0.0-20230608123814-4bd63c2919ab // indirect
github.com/fatih/color v1.15.0 // indirect github.com/fatih/color v1.15.0 // indirect
github.com/fatih/structtag v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 // indirect github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
golang.org/x/sys v0.11.0 // indirect golang.org/x/sys v0.11.0 // indirect
golang.org/x/tools v0.12.0 // indirect golang.org/x/tools v0.12.0 // indirect
) )

26
lexer/error.go

@ -25,17 +25,29 @@ type Error struct {
Column int Column int
Content string Content string
Hint string
Err error Err error
} }
func (err Error) prefix() string {
switch {
case err.Line > 0 || err.Column > 0:
if err.Filename != "" {
return fmt.Sprintf("%s:%v:%v", err.Filename, err.Line, err.Column)
}
return fmt.Sprintf("%v:%v", err.Line, err.Column)
default:
return err.Filename
}
}
func (err Error) Error() string { func (err Error) Error() string {
var s []string var s []string
switch { prefix := err.prefix()
case err.Line > 0 || err.Column > 0: if prefix != "" {
s = append(s, fmt.Sprintf("%s:%v:%v", err.Filename, err.Line, err.Column)) s = append(s, prefix)
case err.Filename != "":
s = append(s, err.Filename)
} }
if err.Err != nil { if err.Err != nil {
@ -46,6 +58,10 @@ func (err Error) Error() string {
s = append(s, fmt.Sprintf("%q", err.Content)) s = append(s, fmt.Sprintf("%q", err.Content))
} }
if err.Hint != "" {
s = append(s, err.Hint)
}
return strings.Join(s, ": ") return strings.Join(s, ": ")
} }

69
reflection/error.go

@ -0,0 +1,69 @@
package reflection
import (
"bytes"
"fmt"
"reflect"
)
// An InvalidUnmarshalError describes an invalid argument passed to New.
// (It must be a non-nil pointer.)
type InvalidUnmarshalError struct {
Method string
Prefix string
reflect.Type
}
func (e *InvalidUnmarshalError) Error() string {
var buf bytes.Buffer
if e.Prefix != "" {
_, _ = fmt.Fprintf(&buf, "%s: ", e.Prefix)
}
method := e.Method
if method == "" {
method = "New"
}
_, _ = fmt.Fprintf(&buf, "%s(", method)
switch {
case e.Type == nil:
_, _ = buf.WriteString("nil")
case e.Type.Kind() != reflect.Pointer:
_, _ = fmt.Fprintf(&buf, "%s %s", "non-pointer", e.Type.String())
default:
_, _ = fmt.Fprintf(&buf, "%s %s", "nil", e.Type.String())
}
_, _ = buf.WriteString(")")
return buf.String()
}
// An UnmarshalTypeError tells something went wrong while processing
// a type.
type UnmarshalTypeError struct {
Prefix string
Err error
reflect.Type
}
func (e UnmarshalTypeError) Unwrap() error {
return e.Err
}
func (e UnmarshalTypeError) Error() string {
var buf bytes.Buffer
if e.Prefix != "" {
_, _ = fmt.Fprintf(&buf, "%s: ", e.Prefix)
}
_, _ = fmt.Fprintf(&buf, "%s: %s", e.Type.String(), e.Err)
return buf.String()
}

16
reflection/reflect_help.go

@ -0,0 +1,16 @@
package reflection
import (
"fmt"
"reflect"
)
func structFieldName(sf reflect.StructField) string {
if sf.Anonymous {
idx := sf.Index[len(sf.Index)-1]
return fmt.Sprintf("%s-%v", "anonymous", idx)
}
return sf.Name
}

40
reflection/reflection.go

@ -0,0 +1,40 @@
// Package reflection helps Marshalling/Unmarshalling data to structs
package reflection
import (
"reflect"
)
// Reflection provides Marshalling/Unmarshalling oriented view
// of a value
type Reflection struct {
v reflect.Value
types map[reflect.Type]TypeInfo
}
// Value returns the object it reflects
func (r *Reflection) Value() any {
return r.v.Interface()
}
// New creates a Reflection of the given pointer
func New(v any) (*Reflection, error) {
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Pointer || rv.IsNil() {
err := &InvalidUnmarshalError{Type: rv.Type()}
return nil, err
}
r := &Reflection{
v: rv,
types: make(map[reflect.Type]TypeInfo),
}
if err := r.scan(); err != nil {
return nil, err
}
return r, nil
}

37
reflection/scan.go

@ -0,0 +1,37 @@
package reflection
import (
"reflect"
)
func (r *Reflection) scanType(rt reflect.Type) error {
switch rt.Kind() {
case reflect.Pointer, reflect.Slice:
rt = rt.Elem()
}
if _, ok := r.types[rt]; ok {
// cached
return nil
}
ti, err := NewTypeInfo(rt)
if err != nil {
return &UnmarshalTypeError{Type: rt, Err: err}
}
r.types[rt] = ti
for i := range ti.fields {
err := r.scanType(ti.fields[i].sf.Type)
if err != nil {
return err
}
}
return nil
}
func (r *Reflection) scan() error {
return r.scanType(r.v.Type())
}

108
reflection/typeinfo.go

@ -0,0 +1,108 @@
package reflection
import (
"log"
"reflect"
"github.com/fatih/structtag"
"github.com/pkg/errors"
)
// TypeInfo ...
type TypeInfo struct {
rt reflect.Type
fields []TypeInfoField
}
// Type ...
func (ti *TypeInfo) Type() reflect.Type {
return ti.rt
}
// Kind ...
func (ti *TypeInfo) Kind() reflect.Kind {
return ti.rt.Kind()
}
// TypeInfoField ...
type TypeInfoField struct {
sf reflect.StructField
tags *structtag.Tags
Name string
}
// Type ...
func (tif *TypeInfoField) Type() reflect.Type {
return tif.sf.Type
}
// Kind ...
func (tif *TypeInfoField) Kind() reflect.Kind {
return tif.sf.Type.Kind()
}
// Tag ...
func (tif *TypeInfoField) Tag(key string) (*structtag.Tag, bool) {
if tif.tags != nil {
tag, _ := tif.tags.Get(key)
if tag != nil {
return tag, true
}
}
return nil, false
}
// NewTypeInfo ...
func NewTypeInfo(rt reflect.Type) (TypeInfo, error) {
log.Printf("%s.%s: %s (%s)", "reflection", "NewTypeInfo", rt, rt.Kind())
ti := TypeInfo{
rt: rt,
}
err := ti.init()
return ti, err
}
func (ti *TypeInfo) init() error {
switch ti.Kind() {
case reflect.Struct:
return ti.initStruct()
default:
return nil
}
}
func (ti *TypeInfo) initStruct() error {
// load fields
n := ti.rt.NumField()
fields := make([]TypeInfoField, 0, n)
for i := 0; i < n; i++ {
sf := ti.rt.Field(i)
tags, err := structtag.Parse(string(sf.Tag))
if err != nil {
// tag parse error
err = &UnmarshalTypeError{
Type: ti.rt,
Err: errors.Wrap(err, structFieldName(sf)),
}
return err
}
if sf.IsExported() {
log.Printf("%s.%s: [%v]: %q %s (%s)",
"reflection", "NewTypeInfo",
i, structFieldName(sf), sf.Type, sf.Type.Kind())
fields = append(fields, TypeInfoField{
sf: sf,
tags: tags,
})
}
}
ti.fields = fields
return nil
}
Loading…
Cancel
Save