github.com/google/skylark@v0.0.0-20181101142754-a5f7082aabed/skylarkstruct/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 skylarkstruct defines the Skylark 'struct' type, 6 // an optional language extension. 7 package skylarkstruct 8 9 // It is tempting to introduce a variant of Struct that is a wrapper 10 // around a Go struct value, for stronger typing guarantees and more 11 // efficient and convenient field lookup. However: 12 // 1) all fields of Skylark structs are optional, so we cannot represent 13 // them using more specific types such as String, Int, *Depset, and 14 // *File, as such types give no way to represent missing fields. 15 // 2) the efficiency gain of direct struct field access is rather 16 // marginal: finding the index of a field by binary searching on the 17 // sorted list of field names is quite fast compared to the other 18 // overheads. 19 // 3) the gains in compactness and spatial locality are also rather 20 // marginal: the array behind the []entry slice is (due to field name 21 // strings) only a factor of 2 larger than the corresponding Go struct 22 // would be, and, like the Go struct, requires only a single allocation. 23 24 import ( 25 "bytes" 26 "encoding/json" 27 "fmt" 28 "sort" 29 30 "github.com/google/skylark" 31 "github.com/google/skylark/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 Skylark environment like so: 38 // 39 // globals := skylark.StringDict{ 40 // "struct": skylark.NewBuiltin("struct", skylarkstruct.Make), 41 // } 42 // 43 func Make(_ *skylark.Thread, _ *skylark.Builtin, args skylark.Tuple, kwargs []skylark.Tuple) (skylark.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 skylark.String.) 52 func FromKeywords(constructor skylark.Value, kwargs []skylark.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].(skylark.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 whose elements are those of d. 70 // The constructor parameter specifies the constructor; use Default for an ordinary struct. 71 func FromStringDict(constructor skylark.Value, d skylark.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 Skylark 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 skylark.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 = skylark.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 // not to_{proto,json} 118 value skylark.Value 119 } 120 121 var ( 122 _ skylark.HasAttrs = (*Struct)(nil) 123 _ skylark.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 skylark.StringDict) { 128 for _, e := range s.entries { 129 d[e.name] = e.value 130 } 131 } 132 133 func (s *Struct) String() string { 134 var buf bytes.Buffer 135 if s.constructor == Default { 136 // NB: The Java implementation always prints struct 137 // even for Bazel provider instances. 138 buf.WriteString("struct") // avoid String()'s quotation 139 } else { 140 buf.WriteString(s.constructor.String()) 141 } 142 buf.WriteByte('(') 143 for i, e := range s.entries { 144 if i > 0 { 145 buf.WriteString(", ") 146 } 147 buf.WriteString(e.name) 148 buf.WriteString(" = ") 149 buf.WriteString(e.value.String()) 150 } 151 buf.WriteByte(')') 152 return buf.String() 153 } 154 155 // Constructor returns the constructor used to create this struct. 156 func (s *Struct) Constructor() skylark.Value { return s.constructor } 157 158 func (s *Struct) Type() string { return "struct" } 159 func (s *Struct) Truth() skylark.Bool { return true } // even when empty 160 func (s *Struct) Hash() (uint32, error) { 161 // Same algorithm as Tuple.hash, but with different primes. 162 var x, m uint32 = 8731, 9839 163 for _, e := range s.entries { 164 namehash, _ := skylark.String(e.name).Hash() 165 x = x ^ 3*namehash 166 y, err := e.value.Hash() 167 if err != nil { 168 return 0, err 169 } 170 x = x ^ y*m 171 m += 7349 172 } 173 return x, nil 174 } 175 func (s *Struct) Freeze() { 176 for _, e := range s.entries { 177 e.value.Freeze() 178 } 179 } 180 181 func (x *Struct) Binary(op syntax.Token, y skylark.Value, side skylark.Side) (skylark.Value, error) { 182 if y, ok := y.(*Struct); ok && op == syntax.PLUS { 183 if side == skylark.Right { 184 x, y = y, x 185 } 186 187 if eq, err := skylark.Equal(x.constructor, y.constructor); err != nil { 188 return nil, fmt.Errorf("in %s + %s: error comparing constructors: %v", 189 x.constructor, y.constructor, err) 190 } else if !eq { 191 return nil, fmt.Errorf("cannot add structs of different constructors: %s + %s", 192 x.constructor, y.constructor) 193 } 194 195 z := make(skylark.StringDict, x.len()+y.len()) 196 for _, e := range x.entries { 197 z[e.name] = e.value 198 } 199 for _, e := range y.entries { 200 z[e.name] = e.value 201 } 202 203 return FromStringDict(x.constructor, z), nil 204 } 205 return nil, nil // unhandled 206 } 207 208 // Attr returns the value of the specified field, 209 // or deprecated method if the name is "to_json" or "to_proto" 210 // and the struct has no field of that name. 211 func (s *Struct) Attr(name string) (skylark.Value, error) { 212 // Binary search the entries. 213 // This implementation is a specialization of 214 // sort.Search that avoids dynamic dispatch. 215 n := len(s.entries) 216 i, j := 0, n 217 for i < j { 218 h := int(uint(i+j) >> 1) 219 if s.entries[h].name < name { 220 i = h + 1 221 } else { 222 j = h 223 } 224 } 225 if i < n && s.entries[i].name == name { 226 return s.entries[i].value, nil 227 } 228 229 // TODO(adonovan): there may be a nice feature for core 230 // skylark.Value here, especially for JSON, but the current 231 // features are incomplete and underspecified. 232 // 233 // to_{json,proto} are deprecated, appropriately; see Google issue b/36412967. 234 switch name { 235 case "to_json", "to_proto": 236 return skylark.NewBuiltin(name, func(thread *skylark.Thread, fn *skylark.Builtin, args skylark.Tuple, kwargs []skylark.Tuple) (skylark.Value, error) { 237 var buf bytes.Buffer 238 var err error 239 if name == "to_json" { 240 err = writeJSON(&buf, s) 241 } else { 242 err = writeProtoStruct(&buf, 0, s) 243 } 244 if err != nil { 245 // TODO(adonovan): improve error error messages 246 // to show the path through the object graph. 247 return nil, err 248 } 249 return skylark.String(buf.String()), nil 250 }), nil 251 } 252 253 var ctor string 254 if s.constructor != Default { 255 ctor = s.constructor.String() + " " 256 } 257 return nil, fmt.Errorf("%sstruct has no .%s attribute", ctor, name) 258 } 259 260 func writeProtoStruct(out *bytes.Buffer, depth int, s *Struct) error { 261 for _, e := range s.entries { 262 if err := writeProtoField(out, depth, e.name, e.value); err != nil { 263 return err 264 } 265 } 266 return nil 267 } 268 269 func writeProtoField(out *bytes.Buffer, depth int, field string, v skylark.Value) error { 270 if depth > 16 { 271 return fmt.Errorf("to_proto: depth limit exceeded") 272 } 273 274 switch v := v.(type) { 275 case *Struct: 276 fmt.Fprintf(out, "%*s%s {\n", 2*depth, "", field) 277 if err := writeProtoStruct(out, depth+1, v); err != nil { 278 return err 279 } 280 fmt.Fprintf(out, "%*s}\n", 2*depth, "") 281 return nil 282 283 case *skylark.List, skylark.Tuple: 284 iter := skylark.Iterate(v) 285 defer iter.Done() 286 var elem skylark.Value 287 for iter.Next(&elem) { 288 if err := writeProtoField(out, depth, field, elem); err != nil { 289 return err 290 } 291 } 292 return nil 293 } 294 295 // scalars 296 fmt.Fprintf(out, "%*s%s: ", 2*depth, "", field) 297 switch v := v.(type) { 298 case skylark.Bool: 299 fmt.Fprintf(out, "%t", v) 300 301 case skylark.Int: 302 // TODO(adonovan): limits? 303 out.WriteString(v.String()) 304 305 case skylark.Float: 306 fmt.Fprintf(out, "%g", v) 307 308 case skylark.String: 309 fmt.Fprintf(out, "%q", string(v)) 310 311 default: 312 return fmt.Errorf("cannot convert %s to proto", v.Type()) 313 } 314 out.WriteByte('\n') 315 return nil 316 } 317 318 // writeJSON writes the JSON representation of a Skylark value to out. 319 // TODO(adonovan): there may be a nice feature for core skylark.Value here, 320 // but the current feature is incomplete and underspecified. 321 func writeJSON(out *bytes.Buffer, v skylark.Value) error { 322 switch v := v.(type) { 323 case skylark.NoneType: 324 out.WriteString("null") 325 case skylark.Bool: 326 fmt.Fprintf(out, "%t", v) 327 case skylark.Int: 328 // TODO(adonovan): test large numbers. 329 out.WriteString(v.String()) 330 case skylark.Float: 331 // TODO(adonovan): test. 332 fmt.Fprintf(out, "%g", v) 333 case skylark.String: 334 s := string(v) 335 if goQuoteIsSafe(s) { 336 fmt.Fprintf(out, "%q", s) 337 } else { 338 // vanishingly rare for text strings 339 data, _ := json.Marshal(s) 340 out.Write(data) 341 } 342 case skylark.Indexable: // Tuple, List 343 out.WriteByte('[') 344 for i, n := 0, skylark.Len(v); i < n; i++ { 345 if i > 0 { 346 out.WriteString(", ") 347 } 348 if err := writeJSON(out, v.Index(i)); err != nil { 349 return err 350 } 351 } 352 out.WriteByte(']') 353 case *Struct: 354 out.WriteByte('{') 355 for i, e := range v.entries { 356 if i > 0 { 357 out.WriteString(", ") 358 } 359 if err := writeJSON(out, skylark.String(e.name)); err != nil { 360 return err 361 } 362 out.WriteString(": ") 363 if err := writeJSON(out, e.value); err != nil { 364 return err 365 } 366 } 367 out.WriteByte('}') 368 default: 369 return fmt.Errorf("cannot convert %s to JSON", v.Type()) 370 } 371 return nil 372 } 373 374 func goQuoteIsSafe(s string) bool { 375 for _, r := range s { 376 // JSON doesn't like Go's \xHH escapes for ASCII control codes, 377 // nor its \UHHHHHHHH escapes for runes >16 bits. 378 if r < 0x20 || r >= 0x10000 { 379 return false 380 } 381 } 382 return true 383 } 384 385 func (s *Struct) len() int { return len(s.entries) } 386 387 // AttrNames returns a new sorted list of the struct fields. 388 // 389 // Unlike in the Java implementation, 390 // the deprecated struct methods "to_json" and "to_proto" do not 391 // appear in AttrNames, and hence dir(struct), since that would force 392 // the majority to have to ignore them, but they may nonetheless be 393 // called if the struct does not have fields of these names. Ideally 394 // these will go away soon. See Google issue b/36412967. 395 func (s *Struct) AttrNames() []string { 396 names := make([]string, len(s.entries)) 397 for i, e := range s.entries { 398 names[i] = e.name 399 } 400 return names 401 } 402 403 func (x *Struct) CompareSameType(op syntax.Token, y_ skylark.Value, depth int) (bool, error) { 404 y := y_.(*Struct) 405 switch op { 406 case syntax.EQL: 407 return structsEqual(x, y, depth) 408 case syntax.NEQ: 409 eq, err := structsEqual(x, y, depth) 410 return !eq, err 411 default: 412 return false, fmt.Errorf("%s %s %s not implemented", x.Type(), op, y.Type()) 413 } 414 } 415 416 func structsEqual(x, y *Struct, depth int) (bool, error) { 417 if x.len() != y.len() { 418 return false, nil 419 } 420 421 if eq, err := skylark.Equal(x.constructor, y.constructor); err != nil { 422 return false, fmt.Errorf("error comparing struct constructors %v and %v: %v", 423 x.constructor, y.constructor, err) 424 } else if !eq { 425 return false, nil 426 } 427 428 for i, n := 0, x.len(); i < n; i++ { 429 if x.entries[i].name != y.entries[i].name { 430 return false, nil 431 } else if eq, err := skylark.EqualDepth(x.entries[i].value, y.entries[i].value, depth-1); err != nil { 432 return false, err 433 } else if !eq { 434 return false, nil 435 } 436 } 437 return true, nil 438 }