github.com/emcfarlane/larking@v0.0.0-20220605172417-1704b45ee6c3/starlib/starlarkstruct/struct.go (about)

     1  // https://github.com/google/starlark-go/pull/403
     2  //
     3  // Copyright 2017 The Bazel Authors. All rights reserved.
     4  // Use of this source code is governed by a BSD-style
     5  // license that can be found in the LICENSE file.
     6  
     7  // Package starlarkstruct defines the Starlark types 'struct' and
     8  // 'module', both optional language extensions.
     9  package starlarkstruct
    10  
    11  // It is tempting to introduce a variant of Struct that is a wrapper
    12  // around a Go struct value, for stronger typing guarantees and more
    13  // efficient and convenient field lookup. However:
    14  // 1) all fields of Starlark structs are optional, so we cannot represent
    15  //    them using more specific types such as String, Int, *Depset, and
    16  //    *File, as such types give no way to represent missing fields.
    17  // 2) the efficiency gain of direct struct field access is rather
    18  //    marginal: finding the index of a field by map access is O(1)
    19  //    and is quite fast compared to the other overheads.
    20  // Such an implementation is likely to be more compact than
    21  // the current map-based representation, though.
    22  
    23  import (
    24  	"fmt"
    25  	"strings"
    26  
    27  	"github.com/emcfarlane/larking/starlib/starext"
    28  	"go.starlark.net/starlark"
    29  	"go.starlark.net/syntax"
    30  )
    31  
    32  // Make is the implementation of a built-in function that instantiates
    33  // an immutable struct from the specified keyword arguments.
    34  //
    35  // An application can add 'struct' to the Starlark environment like so:
    36  //
    37  // 	globals := starlark.StringDict{
    38  // 		"struct":  starlark.NewBuiltin("struct", starlarkstruct.Make),
    39  // 	}
    40  //
    41  func Make(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
    42  	if len(args) > 0 {
    43  		return nil, fmt.Errorf("struct: unexpected positional arguments")
    44  	}
    45  	return FromKeywords(Default, kwargs), nil
    46  }
    47  
    48  // FromKeywords returns a new struct instance whose fields are specified by the
    49  // key/value pairs in kwargs.  (Each kwargs[i][0] must be a starlark.String.)
    50  func FromKeywords(constructor starlark.Value, kwargs []starlark.Tuple) *Struct {
    51  	if constructor == nil {
    52  		panic("nil constructor")
    53  	}
    54  
    55  	osd := starext.NewOrderedStringDict(len(kwargs))
    56  	for _, kwarg := range kwargs {
    57  		k := string(kwarg[0].(starlark.String))
    58  		v := kwarg[1]
    59  		osd.Insert(k, v)
    60  	}
    61  	osd.Sort() // sort by key
    62  	return &Struct{
    63  		constructor: constructor,
    64  		osd:         *osd,
    65  	}
    66  }
    67  
    68  // FromStringDict returns a new struct instance whose elements are those of d.
    69  // The constructor parameter specifies the constructor; use Default for an ordinary struct.
    70  func FromStringDict(constructor starlark.Value, d starlark.StringDict) *Struct {
    71  	if constructor == nil {
    72  		panic("nil constructor")
    73  	}
    74  	osd := starext.NewOrderedStringDict(len(d))
    75  	for key, val := range d {
    76  		osd.Insert(key, val)
    77  	}
    78  	osd.Sort() // sort by key
    79  	return &Struct{
    80  		constructor: constructor,
    81  		osd:         *osd,
    82  	}
    83  }
    84  
    85  // OrderedStringDictAsStruct returns a new struct instance whose elements are
    86  // those of osd. The struct owns the dictionary values.
    87  // The constructor parameter specifies the constructor; use Default for an ordinary struct.
    88  func OrderedStringDictAsStruct(constructor starlark.Value, osd *starext.OrderedStringDict) *Struct {
    89  	if constructor == nil {
    90  		panic("nil constructor")
    91  	}
    92  	return &Struct{
    93  		constructor: constructor,
    94  		osd:         *osd,
    95  	}
    96  }
    97  
    98  // FromKeyValues returns a new struct instance with an array of key/value pairs.
    99  // Keys must be of type string, values of type starlark.Value.
   100  func FromKeyValues(constructor starlark.Value, kvs ...any) *Struct {
   101  	if constructor == nil {
   102  		panic("nil constructor")
   103  	}
   104  	osd := starext.NewOrderedStringDict(len(kvs))
   105  	for i := 0; i < len(kvs); i += 2 {
   106  		k, v := kvs[i].(string), kvs[i+1].(starlark.Value)
   107  		osd.Insert(k, v)
   108  	}
   109  	osd.Sort() // sort by key
   110  	return &Struct{
   111  		constructor: constructor,
   112  		osd:         *osd,
   113  	}
   114  }
   115  
   116  // Struct is an immutable Starlark type that maps field names to values.
   117  // It is not iterable and does not support len.
   118  //
   119  // A struct has a constructor, a distinct value that identifies a class
   120  // of structs, and which appears in the struct's string representation.
   121  //
   122  // Operations such as x+y fail if the constructors of the two operands
   123  // are not equal.
   124  //
   125  // The default constructor, Default, is the string "struct", but
   126  // clients may wish to 'brand' structs for their own purposes.
   127  // The constructor value appears in the printed form of the value,
   128  // and is accessible using the Constructor method.
   129  //
   130  // Use Attr to access its fields and AttrNames to enumerate them.
   131  type Struct struct {
   132  	constructor starlark.Value
   133  	osd         starext.OrderedStringDict
   134  	frozen      bool
   135  }
   136  
   137  // Default is the default constructor for structs.
   138  // It is merely the string "struct".
   139  const Default = starlark.String("struct")
   140  
   141  var (
   142  	_ starlark.HasAttrs    = (*Struct)(nil)
   143  	_ starlark.HasBinary   = (*Struct)(nil)
   144  	_ starlark.HasSetField = (*Struct)(nil)
   145  )
   146  
   147  // ToStringDict adds a name/value entry to d for each field of the struct.
   148  func (s *Struct) ToStringDict(d starlark.StringDict) {
   149  	for i := 0; i < s.osd.Len(); i++ {
   150  		k, v := s.osd.KeyIndex(i)
   151  		d[k] = v
   152  	}
   153  }
   154  
   155  func (s *Struct) ToOrderedStringDict(osd *starext.OrderedStringDict) {
   156  	for i := 0; i < s.osd.Len(); i++ {
   157  		k, v := s.osd.KeyIndex(i)
   158  		osd.Insert(k, v)
   159  	}
   160  }
   161  
   162  func (s *Struct) String() string {
   163  	buf := new(strings.Builder)
   164  	if v, ok := s.constructor.(starlark.String); ok {
   165  		// NB: The Java implementation always prints struct
   166  		// even for Bazel provider instances.
   167  		buf.WriteString(string(v)) // avoid String()'s quotation
   168  	} else {
   169  		buf.WriteString(s.constructor.String())
   170  	}
   171  	buf.WriteByte('(')
   172  	for i := 0; i < s.osd.Len(); i++ {
   173  		k, v := s.osd.KeyIndex(i)
   174  		if i > 0 {
   175  			buf.WriteString(", ")
   176  		}
   177  		buf.WriteString(k)
   178  		buf.WriteString(" = ")
   179  		buf.WriteString(v.String())
   180  	}
   181  	buf.WriteByte(')')
   182  	return buf.String()
   183  }
   184  
   185  // Constructor returns the constructor used to create this struct.
   186  func (s *Struct) Constructor() starlark.Value { return s.constructor }
   187  
   188  func (s *Struct) Type() string         { return "struct" }
   189  func (s *Struct) Truth() starlark.Bool { return true } // even when empty
   190  func (s *Struct) Hash() (uint32, error) {
   191  	// Same algorithm as Tuple.hash, but with different primes.
   192  	var x, m uint32 = 8731, 9839
   193  	for i := 0; i < s.osd.Len(); i++ {
   194  		k, v := s.osd.KeyIndex(i)
   195  		namehash, _ := starlark.String(k).Hash()
   196  		x = x ^ 3*namehash
   197  		y, err := v.Hash()
   198  		if err != nil {
   199  			return 0, err
   200  		}
   201  		x = x ^ y*m
   202  		m += 7349
   203  	}
   204  	return x, nil
   205  }
   206  func (s *Struct) Freeze() {
   207  	if s.frozen {
   208  		return
   209  	}
   210  	s.frozen = true
   211  	for i := 0; i < s.osd.Len(); i++ {
   212  		s.osd.Index(i).Freeze()
   213  	}
   214  }
   215  
   216  func (x *Struct) Binary(op syntax.Token, y starlark.Value, side starlark.Side) (starlark.Value, error) {
   217  	if y, ok := y.(*Struct); ok && op == syntax.PLUS {
   218  		if side == starlark.Right {
   219  			x, y = y, x
   220  		}
   221  
   222  		if eq, err := starlark.Equal(x.constructor, y.constructor); err != nil {
   223  			return nil, fmt.Errorf("in %s + %s: error comparing constructors: %v",
   224  				x.constructor, y.constructor, err)
   225  		} else if !eq {
   226  			return nil, fmt.Errorf("cannot add structs of different constructors: %s + %s",
   227  				x.constructor, y.constructor)
   228  		}
   229  
   230  		z := make(starlark.StringDict, x.len()+y.len())
   231  		for i := 0; i < x.osd.Len(); i++ {
   232  			k, v := x.osd.KeyIndex(i)
   233  			z[k] = v
   234  		}
   235  		for i := 0; i < y.osd.Len(); i++ {
   236  			k, v := y.osd.KeyIndex(i)
   237  			z[k] = v
   238  		}
   239  
   240  		return FromStringDict(x.constructor, z), nil
   241  	}
   242  	return nil, nil // unhandled
   243  }
   244  
   245  // Attr returns the value of the specified field.
   246  func (s *Struct) Attr(name string) (starlark.Value, error) {
   247  	if v, ok := s.osd.Get(name); ok {
   248  		return v, nil
   249  	}
   250  	var ctor string
   251  	if s.constructor != Default {
   252  		ctor = s.constructor.String() + " "
   253  	}
   254  	return nil, starlark.NoSuchAttrError(
   255  		fmt.Sprintf("%sstruct has no .%s attribute", ctor, name))
   256  }
   257  
   258  func (s *Struct) len() int { return s.osd.Len() }
   259  
   260  // AttrNames returns a new sorted list of the struct fields.
   261  func (s *Struct) AttrNames() []string { return s.osd.Keys() }
   262  
   263  func (x *Struct) CompareSameType(op syntax.Token, y_ starlark.Value, depth int) (bool, error) {
   264  	y := y_.(*Struct)
   265  	switch op {
   266  	case syntax.EQL:
   267  		return structsEqual(x, y, depth)
   268  	case syntax.NEQ:
   269  		eq, err := structsEqual(x, y, depth)
   270  		return !eq, err
   271  	default:
   272  		return false, fmt.Errorf("%s %s %s not implemented", x.Type(), op, y.Type())
   273  	}
   274  }
   275  
   276  func structsEqual(x, y *Struct, depth int) (bool, error) {
   277  	if x.len() != y.len() {
   278  		return false, nil
   279  	}
   280  
   281  	if eq, err := starlark.Equal(x.constructor, y.constructor); err != nil {
   282  		return false, fmt.Errorf("error comparing struct constructors %v and %v: %v",
   283  			x.constructor, y.constructor, err)
   284  	} else if !eq {
   285  		return false, nil
   286  	}
   287  
   288  	for i := 0; i < x.len(); i++ {
   289  		xkey, xval := x.osd.KeyIndex(i)
   290  		ykey, yval := y.osd.KeyIndex(i)
   291  
   292  		if xkey != ykey {
   293  			return false, nil
   294  		} else if eq, err := starlark.EqualDepth(xval, yval, depth-1); err != nil {
   295  			return false, err
   296  		} else if !eq {
   297  			return false, nil
   298  		}
   299  	}
   300  	return true, nil
   301  
   302  }
   303  
   304  // SetField sets a fields value.
   305  func (s *Struct) SetField(name string, value starlark.Value) error {
   306  	if s.frozen {
   307  		return fmt.Errorf("cannot insert into frozen struct")
   308  	}
   309  	if ok := s.osd.Set(name, value); !ok {
   310  		return fmt.Errorf("invalid field name for struct")
   311  	}
   312  	return nil
   313  }
   314  
   315  func (s *Struct) Len() int                                { return s.osd.Len() }
   316  func (s *Struct) KeyIndex(i int) (string, starlark.Value) { return s.osd.KeyIndex(i) }