github.com/joomcode/cue@v0.4.4-0.20221111115225-539fe3512047/internal/core/adt/feature.go (about)

     1  // Copyright 2020 CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package adt
    16  
    17  import (
    18  	"fmt"
    19  	"strconv"
    20  	"strings"
    21  
    22  	"github.com/joomcode/cue/cue/ast"
    23  	"github.com/joomcode/cue/cue/errors"
    24  	"github.com/joomcode/cue/cue/literal"
    25  	"github.com/joomcode/cue/cue/token"
    26  	"github.com/joomcode/cue/internal"
    27  )
    28  
    29  // A Feature is an encoded form of a label which comprises a compact
    30  // representation of an integer or string label as well as a label type.
    31  type Feature uint32
    32  
    33  // TODO: create labels such that list are sorted first (or last with index.)
    34  
    35  // InvalidLabel is an encoding of an erroneous label.
    36  const (
    37  	InvalidLabel Feature = 0
    38  
    39  	// MaxIndex indicates the maximum number of unique strings that are used for
    40  	// labeles within this CUE implementation.
    41  	MaxIndex = 1<<(32-indexShift) - 1
    42  )
    43  
    44  // These labels can be used for wildcard queries.
    45  var (
    46  	AnyDefinition Feature = makeLabel(MaxIndex, DefinitionLabel)
    47  	AnyHidden     Feature = makeLabel(MaxIndex, HiddenLabel)
    48  	AnyString     Feature = makeLabel(MaxIndex, StringLabel)
    49  	AnyIndex      Feature = makeLabel(MaxIndex, IntLabel)
    50  )
    51  
    52  // A StringIndexer coverts strings to and from an index that is unique for a
    53  // given string.
    54  type StringIndexer interface {
    55  	// ToIndex returns a unique positive index for s (0 < index < 2^28-1).
    56  	//
    57  	// For each pair of strings s and t it must return the same index if and
    58  	// only if s == t.
    59  	StringToIndex(s string) (index int64)
    60  
    61  	// ToString returns a string s for index such that ToIndex(s) == index.
    62  	IndexToString(index int64) string
    63  }
    64  
    65  // SelectorString reports the shortest string representation of f when used as a
    66  // selector.
    67  func (f Feature) SelectorString(index StringIndexer) string {
    68  	x := f.safeIndex()
    69  	switch f.Typ() {
    70  	case IntLabel:
    71  		return strconv.Itoa(int(x))
    72  	case StringLabel:
    73  		s := index.IndexToString(x)
    74  		if ast.IsValidIdent(s) && !internal.IsDefOrHidden(s) {
    75  			return s
    76  		}
    77  		return literal.String.Quote(s)
    78  	default:
    79  		return f.IdentString(index)
    80  	}
    81  }
    82  
    83  // IdentString reports the identifier of f. The result is undefined if f
    84  // is not an identifier label.
    85  func (f Feature) IdentString(index StringIndexer) string {
    86  	s := index.IndexToString(f.safeIndex())
    87  	if f.IsHidden() {
    88  		if p := strings.IndexByte(s, '\x00'); p >= 0 {
    89  			s = s[:p]
    90  		}
    91  	}
    92  	return s
    93  }
    94  
    95  // PkgID returns the package identifier, composed of the module and package
    96  // name, associated with this identifier. It will return "" if this is not
    97  // a hidden label.
    98  func (f Feature) PkgID(index StringIndexer) string {
    99  	if !f.IsHidden() {
   100  		return ""
   101  	}
   102  	s := index.IndexToString(f.safeIndex())
   103  	if p := strings.IndexByte(s, '\x00'); p >= 0 {
   104  		s = s[p+1:]
   105  	}
   106  	return s
   107  }
   108  
   109  // StringValue reports the string value of f, which must be a string label.
   110  func (f Feature) StringValue(index StringIndexer) string {
   111  	if !f.IsString() {
   112  		panic("not a string label")
   113  	}
   114  	x := f.safeIndex()
   115  	return index.IndexToString(x)
   116  }
   117  
   118  // ToValue converts a label to a value, which will be a Num for integer labels
   119  // and a String for string labels. It panics when f is not a regular label.
   120  func (f Feature) ToValue(ctx *OpContext) Value {
   121  	if !f.IsRegular() {
   122  		panic("not a regular label")
   123  	}
   124  	// TODO: Handle special regular values: invalid and AnyRegular.
   125  	if f.IsInt() {
   126  		return ctx.NewInt64(int64(f.Index()))
   127  	}
   128  	x := f.safeIndex()
   129  	str := ctx.IndexToString(x)
   130  	return ctx.NewString(str)
   131  }
   132  
   133  // StringLabel converts s to a string label.
   134  func (c *OpContext) StringLabel(s string) Feature {
   135  	return labelFromValue(c, nil, &String{Str: s})
   136  }
   137  
   138  // MakeStringLabel creates a label for the given string.
   139  func MakeStringLabel(r StringIndexer, s string) Feature {
   140  	i := r.StringToIndex(s)
   141  
   142  	// TODO: set position if it exists.
   143  	f, err := MakeLabel(nil, i, StringLabel)
   144  	if err != nil {
   145  		panic("out of free string slots")
   146  	}
   147  	return f
   148  }
   149  
   150  // MakeIdentLabel creates a label for the given identifier.
   151  func MakeIdentLabel(r StringIndexer, s, pkgpath string) Feature {
   152  	t := StringLabel
   153  	switch {
   154  	case strings.HasPrefix(s, "_#"):
   155  		t = HiddenDefinitionLabel
   156  		s = fmt.Sprintf("%s\x00%s", s, pkgpath)
   157  	case strings.HasPrefix(s, "#"):
   158  		t = DefinitionLabel
   159  	case strings.HasPrefix(s, "_"):
   160  		s = fmt.Sprintf("%s\x00%s", s, pkgpath)
   161  		t = HiddenLabel
   162  	}
   163  	i := r.StringToIndex(s)
   164  	f, err := MakeLabel(nil, i, t)
   165  	if err != nil {
   166  		panic("out of free string slots")
   167  	}
   168  	return f
   169  }
   170  
   171  const msgGround = "invalid non-ground value %s (must be concrete %s)"
   172  
   173  func labelFromValue(c *OpContext, src Expr, v Value) Feature {
   174  	var i int64
   175  	var t FeatureType
   176  	if isError(v) {
   177  		return InvalidLabel
   178  	}
   179  	switch v.Kind() {
   180  	case IntKind, NumKind:
   181  		x, _ := Unwrap(v).(*Num)
   182  		if x == nil {
   183  			c.addErrf(IncompleteError, pos(v), msgGround, v, "int")
   184  			return InvalidLabel
   185  		}
   186  		t = IntLabel
   187  		var err error
   188  		i, err = x.X.Int64()
   189  		if err != nil || x.K != IntKind {
   190  			if src == nil {
   191  				src = v
   192  			}
   193  			c.AddErrf("invalid index %v: %v", src, err)
   194  			return InvalidLabel
   195  		}
   196  		if i < 0 {
   197  			switch src.(type) {
   198  			case nil, *Num, *UnaryExpr:
   199  				// If the value is a constant, we know it is always an error.
   200  				// UnaryExpr is an approximation for a constant value here.
   201  				c.AddErrf("invalid index %s (index must be non-negative)", x)
   202  			default:
   203  				// Use a different message is it is the result of evaluation.
   204  				c.AddErrf("index %s out of range [%s]", src, x)
   205  			}
   206  			return InvalidLabel
   207  		}
   208  
   209  	case StringKind:
   210  		x, _ := Unwrap(v).(*String)
   211  		if x == nil {
   212  			c.addErrf(IncompleteError, pos(v), msgGround, v, "string")
   213  			return InvalidLabel
   214  		}
   215  		t = StringLabel
   216  		i = c.StringToIndex(x.Str)
   217  
   218  	default:
   219  		if src != nil {
   220  			c.AddErrf("invalid index %s (invalid type %v)", src, v.Kind())
   221  		} else {
   222  			c.AddErrf("invalid index type %v", v.Kind())
   223  		}
   224  		return InvalidLabel
   225  	}
   226  
   227  	// TODO: set position if it exists.
   228  	f, err := MakeLabel(nil, i, t)
   229  	if err != nil {
   230  		c.AddErr(err)
   231  	}
   232  	return f
   233  }
   234  
   235  // MakeLabel creates a label. It reports an error if the index is out of range.
   236  func MakeLabel(src ast.Node, index int64, f FeatureType) (Feature, errors.Error) {
   237  	if 0 > index || index > MaxIndex-1 {
   238  		p := token.NoPos
   239  		if src != nil {
   240  			p = src.Pos()
   241  		}
   242  		return InvalidLabel,
   243  			errors.Newf(p, "int label out of range (%d not >=0 and <= %d)",
   244  				index, MaxIndex-1)
   245  	}
   246  	return Feature(index)<<indexShift | Feature(f), nil
   247  }
   248  
   249  func makeLabel(index int64, f FeatureType) Feature {
   250  	return Feature(index)<<indexShift | Feature(f)
   251  }
   252  
   253  // A FeatureType indicates the type of label.
   254  type FeatureType int8
   255  
   256  const (
   257  	InvalidLabelType FeatureType = iota
   258  	StringLabel
   259  	IntLabel
   260  	DefinitionLabel
   261  	HiddenLabel
   262  	HiddenDefinitionLabel
   263  )
   264  
   265  const (
   266  	fTypeMask Feature = 0b1111
   267  
   268  	indexShift = 4
   269  )
   270  
   271  func (f FeatureType) IsDef() bool {
   272  	return f == DefinitionLabel || f == HiddenDefinitionLabel
   273  }
   274  
   275  func (f FeatureType) IsHidden() bool {
   276  	return f == HiddenLabel || f == HiddenDefinitionLabel
   277  }
   278  
   279  // IsValid reports whether f is a valid label.
   280  func (f Feature) IsValid() bool { return f != InvalidLabel }
   281  
   282  // Typ reports the type of label.
   283  func (f Feature) Typ() FeatureType { return FeatureType(f & fTypeMask) }
   284  
   285  // IsRegular reports whether a label represents a data field.
   286  func (f Feature) IsRegular() bool {
   287  	t := f.Typ()
   288  	return t == IntLabel || t == StringLabel
   289  }
   290  
   291  // IsString reports whether a label represents a regular field.
   292  func (f Feature) IsString() bool { return f.Typ() == StringLabel }
   293  
   294  // IsDef reports whether the label is a definition (an identifier starting with
   295  // # or #_.
   296  func (f Feature) IsDef() bool {
   297  	return f.Typ().IsDef()
   298  }
   299  
   300  // IsInt reports whether this is an integer index.
   301  func (f Feature) IsInt() bool { return f.Typ() == IntLabel }
   302  
   303  // IsHidden reports whether this label is hidden (an identifier starting with
   304  // _ or #_).
   305  func (f Feature) IsHidden() bool {
   306  	return f.Typ().IsHidden()
   307  }
   308  
   309  // Index reports the abstract index associated with f.
   310  func (f Feature) Index() int {
   311  	return int(f >> indexShift)
   312  }
   313  
   314  // SafeIndex reports the abstract index associated with f, setting MaxIndex to 0.
   315  func (f Feature) safeIndex() int64 {
   316  	x := int(f >> indexShift)
   317  	if x == MaxIndex {
   318  		x = 0 // Safety, MaxIndex means any
   319  	}
   320  	return int64(x)
   321  }
   322  
   323  // TODO: should let declarations be implemented as fields?
   324  // func (f Feature) isLet() bool  { return f.typ() == letLabel }