go.starlark.net@v0.0.0-20231101134539-556fd59b42f6/starlarkstruct/struct.go (about)

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