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) }