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  }