github.com/ndau/noms@v1.0.5/go/types/path.go (about)

     1  // Copyright 2016 Attic Labs, Inc. All rights reserved.
     2  // Licensed under the Apache License, version 2.0:
     3  // http://www.apache.org/licenses/LICENSE-2.0
     4  
     5  package types
     6  
     7  import (
     8  	"bytes"
     9  	"errors"
    10  	"fmt"
    11  	"math"
    12  	"regexp"
    13  	"strconv"
    14  	"strings"
    15  
    16  	"github.com/ndau/noms/go/d"
    17  	"github.com/ndau/noms/go/hash"
    18  )
    19  
    20  // For an annotation like @type, 1st capture group is the annotation.
    21  // For @at(42), 1st capture group is the annotation and 3rd is the parameter.
    22  // Note, @at() is valid under this regexp, code should deal with the error.
    23  var annotationRe = regexp.MustCompile(`^([a-z]+)(\(([\w\-"']*)\))?`)
    24  
    25  // A Path locates a value in Noms relative to some other value. For locating
    26  // values absolutely within a database, see AbsolutePath. To locate values
    27  // globally, see Spec.
    28  //
    29  // For more details, see:
    30  // https://github.com/ndau/noms/blob/master/doc/spelling.md.
    31  type Path []PathPart
    32  
    33  type PathPart interface {
    34  	Resolve(v Value, vr ValueReader) Value
    35  	String() string
    36  }
    37  
    38  // ParsePath parses str into a Path, or returns an error if parsing failed.
    39  func ParsePath(str string) (Path, error) {
    40  	if str == "" {
    41  		return Path{}, errors.New("Empty path")
    42  	}
    43  	return constructPath(Path{}, str)
    44  }
    45  
    46  // MustParsePath parses str into a Path, or panics if parsing failed.
    47  func MustParsePath(str string) Path {
    48  	p, err := ParsePath(str)
    49  	if err != nil {
    50  		panic(err)
    51  	}
    52  	return p
    53  }
    54  
    55  type keyIndexable interface {
    56  	setIntoKey(v bool) keyIndexable
    57  }
    58  
    59  func constructPath(p Path, str string) (Path, error) {
    60  	if len(str) == 0 {
    61  		return p, nil
    62  	}
    63  
    64  	op, tail := str[0], str[1:]
    65  
    66  	switch op {
    67  	case '.':
    68  		idx := fieldNameComponentRe.FindIndex([]byte(tail))
    69  		if idx == nil {
    70  			return Path{}, errors.New("Invalid field: " + tail)
    71  		}
    72  		p = append(p, FieldPath{tail[:idx[1]]})
    73  		return constructPath(p, tail[idx[1]:])
    74  
    75  	case '[':
    76  		if len(tail) == 0 {
    77  			return Path{}, errors.New("Path ends in [")
    78  		}
    79  
    80  		idx, h, rem, err := ParsePathIndex(tail)
    81  		if err != nil {
    82  			return Path{}, err
    83  		}
    84  		if !strings.HasPrefix(rem, "]") {
    85  			return Path{}, errors.New("[ is missing closing ]")
    86  		}
    87  		d.PanicIfTrue(idx == nil && h.IsEmpty())
    88  		d.PanicIfTrue(idx != nil && !h.IsEmpty())
    89  
    90  		if idx != nil {
    91  			p = append(p, NewIndexPath(idx))
    92  		} else {
    93  			p = append(p, NewHashIndexPath(h))
    94  		}
    95  		return constructPath(p, rem[1:])
    96  
    97  	case '@':
    98  		ann, hasArg, arg, rem := getAnnotation(tail)
    99  
   100  		switch ann {
   101  		case "at":
   102  			if arg == "" {
   103  				return Path{}, fmt.Errorf("@at annotation requires a position argument")
   104  			}
   105  			idx, err := strconv.ParseInt(arg, 10, 64)
   106  			if err != nil {
   107  				return Path{}, fmt.Errorf("Invalid position: %s", arg)
   108  			}
   109  			return constructPath(append(p, NewAtAnnotation(idx)), rem)
   110  
   111  		case "key":
   112  			if hasArg {
   113  				return Path{}, fmt.Errorf("@key annotation does not support arguments")
   114  			}
   115  			if len(p) == 0 {
   116  				return Path{}, fmt.Errorf("Cannot use @key annotation at beginning of path")
   117  			}
   118  			lastPart := p[len(p)-1]
   119  			if ki, ok := lastPart.(keyIndexable); ok {
   120  				p[len(p)-1] = ki.setIntoKey(true).(PathPart)
   121  				return constructPath(p, rem)
   122  			}
   123  			return Path{}, fmt.Errorf("Cannot use @key annotation on: %s", lastPart.String())
   124  
   125  		case "target":
   126  			if hasArg {
   127  				return Path{}, fmt.Errorf("@target annotation does not support arguments")
   128  			}
   129  			return constructPath(append(p, TargetAnnotation{}), rem)
   130  
   131  		case "type":
   132  			if hasArg {
   133  				return Path{}, fmt.Errorf("@type annotation does not support arguments")
   134  			}
   135  			return constructPath(append(p, TypeAnnotation{}), rem)
   136  
   137  		default:
   138  			return Path{}, fmt.Errorf("Unsupported annotation: @%s", ann)
   139  		}
   140  
   141  	case ']':
   142  		return Path{}, errors.New("] is missing opening [")
   143  
   144  	default:
   145  		return Path{}, fmt.Errorf("Invalid operator: %c", op)
   146  	}
   147  }
   148  
   149  // Resolve resolves a path relative to some value.
   150  // A ValueReader is required to resolve paths that contain the @target annotation.
   151  func (p Path) Resolve(v Value, vr ValueReader) (resolved Value) {
   152  	resolved = v
   153  	for _, part := range p {
   154  		if resolved == nil {
   155  			break
   156  		}
   157  		resolved = part.Resolve(resolved, vr)
   158  	}
   159  
   160  	return
   161  }
   162  
   163  func (p Path) Equals(o Path) bool {
   164  	if len(p) != len(o) {
   165  		return false
   166  	}
   167  	for i, pp := range p {
   168  		if pp != o[i] {
   169  			return false
   170  		}
   171  	}
   172  	return true
   173  }
   174  
   175  // Append makes a copy of a p and appends the PathPart 'pp' to it.
   176  func (p Path) Append(pp PathPart) Path {
   177  	p1 := make(Path, len(p), len(p)+1)
   178  	copy(p1, p)
   179  	return append(p1, pp)
   180  }
   181  
   182  func (p Path) String() string {
   183  	strs := make([]string, 0, len(p))
   184  	for _, part := range p {
   185  		strs = append(strs, part.String())
   186  	}
   187  	return strings.Join(strs, "")
   188  }
   189  
   190  func (p Path) IsEmpty() bool {
   191  	return len(p) == 0
   192  }
   193  
   194  // FieldPath references Struct field values by name.
   195  type FieldPath struct {
   196  	// The name of the field, e.g. `.Name`.
   197  	Name string
   198  }
   199  
   200  func NewFieldPath(name string) FieldPath {
   201  	return FieldPath{name}
   202  }
   203  
   204  func (fp FieldPath) Resolve(v Value, vr ValueReader) Value {
   205  	switch v := v.(type) {
   206  	case Struct:
   207  		if sv, ok := v.MaybeGet(fp.Name); ok {
   208  			return sv
   209  		}
   210  	case *Type:
   211  		if desc, ok := v.Desc.(StructDesc); ok {
   212  			if df, _ := desc.Field(fp.Name); df != nil {
   213  				return df
   214  			}
   215  		}
   216  	}
   217  	return nil
   218  }
   219  
   220  func (fp FieldPath) String() string {
   221  	return fmt.Sprintf(".%s", fp.Name)
   222  }
   223  
   224  // IndexPath ndexes into Maps and Lists by key or index.
   225  type IndexPath struct {
   226  	// The value of the index, e.g. `[42]` or `["value"]`. If Index is a negative
   227  	// number and the path is resolved in a List, it means index from the back.
   228  	Index Value
   229  	// Whether this index should resolve to the key of a map, given by a `@key`
   230  	// annotation. Typically IntoKey is false, and indices would resolve to the
   231  	// values. E.g. given `{a: 42}` then `["a"]` resolves to `42`. If IntoKey is
   232  	// true, then it resolves to `"a"`. For IndexPath this isn't particularly
   233  	// useful - it's mostly provided for consistency with HashIndexPath - but
   234  	// note that given `{a: 42}` then `["b"]` resolves to nil, not `"b"`.
   235  	IntoKey bool
   236  }
   237  
   238  func NewIndexPath(idx Value) IndexPath {
   239  	return newIndexPath(idx, false)
   240  }
   241  
   242  func NewIndexIntoKeyPath(idx Value) IndexPath {
   243  	return newIndexPath(idx, true)
   244  }
   245  
   246  func ValueCanBePathIndex(v Value) bool {
   247  	k := v.Kind()
   248  	return k == StringKind || k == BoolKind || k == NumberKind
   249  }
   250  
   251  func newIndexPath(idx Value, intoKey bool) IndexPath {
   252  	d.PanicIfFalse(ValueCanBePathIndex(idx))
   253  	return IndexPath{idx, intoKey}
   254  }
   255  
   256  func (ip IndexPath) Resolve(v Value, vr ValueReader) Value {
   257  	seqIndex := func(getter func(i uint64) Value) Value {
   258  		n, ok := ip.Index.(Number)
   259  		if !ok {
   260  			return nil
   261  		}
   262  		f := float64(n)
   263  		if f != math.Trunc(f) {
   264  			return nil
   265  		}
   266  		ai, ok := getAbsoluteIndex(v, int64(f))
   267  		if !ok {
   268  			return nil
   269  		}
   270  		if ip.IntoKey {
   271  			return Number(ai)
   272  		}
   273  		return getter(ai)
   274  	}
   275  
   276  	switch v := v.(type) {
   277  	case List:
   278  		return seqIndex(func(i uint64) Value { return v.Get(i) })
   279  	case *Type:
   280  		if cd, ok := v.Desc.(CompoundDesc); ok {
   281  			return seqIndex(func(i uint64) Value { return cd.ElemTypes[i] })
   282  		}
   283  	case Map:
   284  		if !ip.IntoKey {
   285  			return v.Get(ip.Index)
   286  		}
   287  		if v.Has(ip.Index) {
   288  			return ip.Index
   289  		}
   290  	}
   291  
   292  	return nil
   293  }
   294  
   295  func (ip IndexPath) String() (str string) {
   296  	str = fmt.Sprintf("[%s]", EncodedIndexValue(ip.Index))
   297  	if ip.IntoKey {
   298  		str += "@key"
   299  	}
   300  	return
   301  }
   302  
   303  func (ip IndexPath) setIntoKey(v bool) keyIndexable {
   304  	ip.IntoKey = v
   305  	return ip
   306  }
   307  
   308  // Indexes into Maps by the hash of a key, or a Set by the hash of a value.
   309  type HashIndexPath struct {
   310  	// The hash of the key or value to search for. Maps and Set are ordered, so
   311  	// this in O(log(size)).
   312  	Hash hash.Hash
   313  	// Whether this index should resolve to the key of a map, given by a `@key`
   314  	// annotation. Typically IntoKey is false, and indices would resolve to the
   315  	// values. E.g. given `{a: 42}` and if the hash of `"a"` is `#abcd`, then
   316  	// `[#abcd]` resolves to `42`. If IntoKey is true, then it resolves to `"a"`.
   317  	// This is useful for when Map keys aren't primitive values, e.g. a struct,
   318  	// since struct literals can't be spelled using a Path.
   319  	IntoKey bool
   320  }
   321  
   322  func NewHashIndexPath(h hash.Hash) HashIndexPath {
   323  	return newHashIndexPath(h, false)
   324  }
   325  
   326  func NewHashIndexIntoKeyPath(h hash.Hash) HashIndexPath {
   327  	return newHashIndexPath(h, true)
   328  }
   329  
   330  func newHashIndexPath(h hash.Hash, intoKey bool) HashIndexPath {
   331  	d.PanicIfTrue(h.IsEmpty())
   332  	return HashIndexPath{h, intoKey}
   333  }
   334  
   335  func (hip HashIndexPath) Resolve(v Value, vr ValueReader) (res Value) {
   336  	var seq orderedSequence
   337  	var getCurrentValue func(cur *sequenceCursor) Value
   338  
   339  	switch v := v.(type) {
   340  	case Set:
   341  		// Unclear what the behavior should be if |hip.IntoKey| is true, but ignoring it for sets is arguably correct.
   342  		seq = v.orderedSequence
   343  		getCurrentValue = func(cur *sequenceCursor) Value { return cur.current().(Value) }
   344  	case Map:
   345  		seq = v.orderedSequence
   346  		if hip.IntoKey {
   347  			getCurrentValue = func(cur *sequenceCursor) Value { return cur.current().(mapEntry).key }
   348  		} else {
   349  			getCurrentValue = func(cur *sequenceCursor) Value { return cur.current().(mapEntry).value }
   350  		}
   351  	default:
   352  		return nil
   353  	}
   354  
   355  	cur := newCursorAt(seq, orderedKeyFromHash(hip.Hash), false, false)
   356  	if !cur.valid() {
   357  		return nil
   358  	}
   359  
   360  	if getCurrentKey(cur).h != hip.Hash {
   361  		return nil
   362  	}
   363  
   364  	return getCurrentValue(cur)
   365  }
   366  
   367  func (hip HashIndexPath) String() (str string) {
   368  	str = fmt.Sprintf("[#%s]", hip.Hash.String())
   369  	if hip.IntoKey {
   370  		str += "@key"
   371  	}
   372  	return
   373  }
   374  
   375  func (hip HashIndexPath) setIntoKey(v bool) keyIndexable {
   376  	hip.IntoKey = v
   377  	return hip
   378  }
   379  
   380  // Parse a Noms value from the path index syntax.
   381  // 4 ->          types.Number
   382  // "4" ->        types.String
   383  // true|false -> types.Boolean
   384  // #<chars> ->   hash.Hash
   385  func ParsePathIndex(str string) (idx Value, h hash.Hash, rem string, err error) {
   386  Switch:
   387  	switch str[0] {
   388  	case '"':
   389  		// String is complicated because ] might be quoted, and " or \ might be escaped.
   390  		stringBuf := bytes.Buffer{}
   391  		i := 1
   392  
   393  		for ; i < len(str); i++ {
   394  			c := str[i]
   395  			if c == '"' {
   396  				i++
   397  				break
   398  			}
   399  			if c == '\\' && i < len(str)-1 {
   400  				i++
   401  				c = str[i]
   402  				if c != '\\' && c != '"' {
   403  					err = errors.New(`Only " and \ can be escaped`)
   404  					break Switch
   405  				}
   406  			}
   407  			stringBuf.WriteByte(c)
   408  		}
   409  
   410  		idx = String(stringBuf.String())
   411  		rem = str[i:]
   412  
   413  	default:
   414  		idxStr := str
   415  		sepIdx := strings.Index(str, "]")
   416  		if sepIdx >= 0 {
   417  			idxStr = str[:sepIdx]
   418  			rem = str[sepIdx:]
   419  		}
   420  		if len(idxStr) == 0 {
   421  			err = errors.New("Empty index value")
   422  		} else if idxStr[0] == '#' {
   423  			hashStr := idxStr[1:]
   424  			h, _ = hash.MaybeParse(hashStr)
   425  			if h.IsEmpty() {
   426  				err = errors.New("Invalid hash: " + hashStr)
   427  			}
   428  		} else if idxStr == "true" {
   429  			idx = Bool(true)
   430  		} else if idxStr == "false" {
   431  			idx = Bool(false)
   432  		} else if i, err2 := strconv.ParseFloat(idxStr, 64); err2 == nil {
   433  			// Should we be more strict here? ParseFloat allows leading and trailing dots, and exponents.
   434  			idx = Number(i)
   435  		} else {
   436  			err = errors.New("Invalid index: " + idxStr)
   437  		}
   438  	}
   439  
   440  	return
   441  }
   442  
   443  // TypeAnnotation is a PathPart annotation to resolve to the type of the value
   444  // it's resolved in.
   445  type TypeAnnotation struct {
   446  }
   447  
   448  func (ann TypeAnnotation) Resolve(v Value, vr ValueReader) Value {
   449  	return TypeOf(v)
   450  }
   451  
   452  func (ann TypeAnnotation) String() string {
   453  	return "@type"
   454  }
   455  
   456  // TargetAnnotation is a PathPart annotation to resolve to the targetValue of the Ref it is resolved on.
   457  type TargetAnnotation struct {
   458  }
   459  
   460  func (ann TargetAnnotation) Resolve(v Value, vr ValueReader) Value {
   461  	if vr == nil {
   462  		d.Panic("@target annotation requires a database to resolve against")
   463  	}
   464  	if r, ok := v.(Ref); ok {
   465  		return r.TargetValue(vr)
   466  	} else {
   467  		return nil
   468  	}
   469  }
   470  
   471  func (ann TargetAnnotation) String() string {
   472  	return "@target"
   473  }
   474  
   475  // AtAnnotation is a PathPart annotation that gets the value of a collection at
   476  // a position, rather than a key. This is equivalent to IndexPath for lists,
   477  // but different for sets and maps.
   478  type AtAnnotation struct {
   479  	// Index is the position to resolve at. If negative, it means an index
   480  	// relative to the end of the collection.
   481  	Index int64
   482  	// IntoKey see IndexPath.IntoKey.
   483  	IntoKey bool
   484  }
   485  
   486  func NewAtAnnotation(idx int64) AtAnnotation {
   487  	return AtAnnotation{idx, false}
   488  }
   489  
   490  func NewAtAnnotationIntoKeyPath(idx int64) AtAnnotation {
   491  	return AtAnnotation{idx, true}
   492  }
   493  
   494  func (ann AtAnnotation) Resolve(v Value, vr ValueReader) Value {
   495  	ai, ok := getAbsoluteIndex(v, ann.Index)
   496  	if !ok {
   497  		return nil
   498  	}
   499  
   500  	switch v := v.(type) {
   501  	case List:
   502  		if !ann.IntoKey {
   503  			return v.Get(ai)
   504  		}
   505  	case Set:
   506  		return v.At(ai)
   507  	case Map:
   508  		k, mapv := v.At(ai)
   509  		if ann.IntoKey {
   510  			return k
   511  		}
   512  		return mapv
   513  	case *Type:
   514  		if cd, ok := v.Desc.(CompoundDesc); ok {
   515  			return cd.ElemTypes[ai]
   516  		}
   517  	}
   518  
   519  	return nil
   520  }
   521  
   522  func (ann AtAnnotation) String() (str string) {
   523  	str = fmt.Sprintf("@at(%d)", ann.Index)
   524  	if ann.IntoKey {
   525  		str += "@key"
   526  	}
   527  	return
   528  }
   529  
   530  func (ann AtAnnotation) setIntoKey(v bool) keyIndexable {
   531  	ann.IntoKey = v
   532  	return ann
   533  }
   534  
   535  func getAnnotation(str string) (ann string, hasArg bool, arg, rem string) {
   536  	parts := annotationRe.FindStringSubmatch(str)
   537  	if parts == nil {
   538  		return
   539  	}
   540  
   541  	ann = parts[1]
   542  	hasArg = parts[2] != ""
   543  	arg = parts[3]
   544  	rem = str[len(parts[0]):]
   545  	return
   546  }
   547  
   548  func getAbsoluteIndex(v Value, relIdx int64) (absIdx uint64, ok bool) {
   549  	var l uint64
   550  	switch v := v.(type) {
   551  	case Collection:
   552  		l = v.Len()
   553  	case *Type:
   554  		if cd, cdOK := v.Desc.(CompoundDesc); cdOK {
   555  			l = uint64(len(cd.ElemTypes))
   556  		} else {
   557  			return
   558  		}
   559  	default:
   560  		return
   561  	}
   562  
   563  	if relIdx < 0 {
   564  		if uint64(-relIdx) > l {
   565  			return
   566  		}
   567  		absIdx = l - uint64(-relIdx)
   568  	} else {
   569  		if uint64(relIdx) >= l {
   570  			return
   571  		}
   572  		absIdx = uint64(relIdx)
   573  	}
   574  
   575  	ok = true
   576  	return
   577  }