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