github.com/lab47/exprcore@v0.0.0-20210525052339-fb7d6bd9331e/exprcore/unpack.go (about)

     1  package exprcore
     2  
     3  // This file defines the Unpack helper functions used by
     4  // built-in functions to interpret their call arguments.
     5  
     6  import (
     7  	"fmt"
     8  	"log"
     9  	"reflect"
    10  	"strings"
    11  )
    12  
    13  // An Unpacker defines custom argument unpacking behavior.
    14  // See UnpackArgs.
    15  type Unpacker interface {
    16  	Unpack(v Value) error
    17  }
    18  
    19  // UnpackArgs unpacks the positional and keyword arguments into the
    20  // supplied parameter variables.  pairs is an alternating list of names
    21  // and pointers to variables.
    22  //
    23  // If the variable is a bool, int, string, *List, *Dict, Callable,
    24  // Iterable, or user-defined implementation of Value,
    25  // UnpackArgs performs the appropriate type check.
    26  // An int uses the AsInt32 check.
    27  // If the parameter name ends with "?",
    28  // it and all following parameters are optional.
    29  //
    30  // If the variable implements Unpacker, its Unpack argument
    31  // is called with the argument value, allowing an application
    32  // to define its own argument validation and conversion.
    33  //
    34  // If the variable implements Value, UnpackArgs may call
    35  // its Type() method while constructing the error message.
    36  //
    37  // Beware: an optional *List, *Dict, Callable, Iterable, or Value variable that is
    38  // not assigned is not a valid exprcore Value, so the caller must
    39  // explicitly handle such cases by interpreting nil as None or some
    40  // computed default.
    41  func UnpackArgs(fnname string, args Tuple, kwargs []Tuple, pairs ...interface{}) error {
    42  	nparams := len(pairs) / 2
    43  	var defined intset
    44  	defined.init(nparams)
    45  
    46  	paramName := func(x interface{}) string { // (no free variables)
    47  		name := x.(string)
    48  		if name[len(name)-1] == '?' {
    49  			name = name[:len(name)-1]
    50  		}
    51  		return name
    52  	}
    53  
    54  	// positional arguments
    55  	if len(args) > nparams {
    56  		return fmt.Errorf("%s: got %d arguments, want at most %d",
    57  			fnname, len(args), nparams)
    58  	}
    59  	for i, arg := range args {
    60  		defined.set(i)
    61  		if err := unpackOneArg(arg, pairs[2*i+1]); err != nil {
    62  			name := paramName(pairs[2*i])
    63  			return fmt.Errorf("%s: for parameter %s: %s", fnname, name, err)
    64  		}
    65  	}
    66  
    67  	// keyword arguments
    68  kwloop:
    69  	for _, item := range kwargs {
    70  		name, arg := item[0].(String), item[1]
    71  		for i := 0; i < nparams; i++ {
    72  			if paramName(pairs[2*i]) == string(name) {
    73  				// found it
    74  				if defined.set(i) {
    75  					return fmt.Errorf("%s: got multiple values for keyword argument %s",
    76  						fnname, name)
    77  				}
    78  				ptr := pairs[2*i+1]
    79  				if err := unpackOneArg(arg, ptr); err != nil {
    80  					return fmt.Errorf("%s: for parameter %s: %s", fnname, name, err)
    81  				}
    82  				continue kwloop
    83  			}
    84  		}
    85  		return fmt.Errorf("%s: unexpected keyword argument %s", fnname, name)
    86  	}
    87  
    88  	// Check that all non-optional parameters are defined.
    89  	// (We needn't check the first len(args).)
    90  	for i := len(args); i < nparams; i++ {
    91  		name := pairs[2*i].(string)
    92  		if strings.HasSuffix(name, "?") {
    93  			break // optional
    94  		}
    95  		if !defined.get(i) {
    96  			return fmt.Errorf("%s: missing argument for %s", fnname, name)
    97  		}
    98  	}
    99  
   100  	return nil
   101  }
   102  
   103  // UnpackPositionalArgs unpacks the positional arguments into
   104  // corresponding variables.  Each element of vars is a pointer; see
   105  // UnpackArgs for allowed types and conversions.
   106  //
   107  // UnpackPositionalArgs reports an error if the number of arguments is
   108  // less than min or greater than len(vars), if kwargs is nonempty, or if
   109  // any conversion fails.
   110  func UnpackPositionalArgs(fnname string, args Tuple, kwargs []Tuple, min int, vars ...interface{}) error {
   111  	if len(kwargs) > 0 {
   112  		return fmt.Errorf("%s: unexpected keyword arguments", fnname)
   113  	}
   114  	max := len(vars)
   115  	if len(args) < min {
   116  		var atleast string
   117  		if min < max {
   118  			atleast = "at least "
   119  		}
   120  		return fmt.Errorf("%s: got %d arguments, want %s%d", fnname, len(args), atleast, min)
   121  	}
   122  	if len(args) > max {
   123  		var atmost string
   124  		if max > min {
   125  			atmost = "at most "
   126  		}
   127  		return fmt.Errorf("%s: got %d arguments, want %s%d", fnname, len(args), atmost, max)
   128  	}
   129  	for i, arg := range args {
   130  		if err := unpackOneArg(arg, vars[i]); err != nil {
   131  			return fmt.Errorf("%s: for parameter %d: %s", fnname, i+1, err)
   132  		}
   133  	}
   134  	return nil
   135  }
   136  
   137  func unpackOneArg(v Value, ptr interface{}) error {
   138  	// On failure, don't clobber *ptr.
   139  	switch ptr := ptr.(type) {
   140  	case Unpacker:
   141  		return ptr.Unpack(v)
   142  	case *Value:
   143  		*ptr = v
   144  	case *string:
   145  		s, ok := AsString(v)
   146  		if !ok {
   147  			return fmt.Errorf("got %s, want string", v.Type())
   148  		}
   149  		*ptr = s
   150  	case *bool:
   151  		b, ok := v.(Bool)
   152  		if !ok {
   153  			return fmt.Errorf("got %s, want bool", v.Type())
   154  		}
   155  		*ptr = bool(b)
   156  	case *int:
   157  		i, err := AsInt32(v)
   158  		if err != nil {
   159  			return err
   160  		}
   161  		*ptr = i
   162  	case **List:
   163  		list, ok := v.(*List)
   164  		if !ok {
   165  			return fmt.Errorf("got %s, want list", v.Type())
   166  		}
   167  		*ptr = list
   168  	case **Dict:
   169  		dict, ok := v.(*Dict)
   170  		if !ok {
   171  			return fmt.Errorf("got %s, want dict", v.Type())
   172  		}
   173  		*ptr = dict
   174  	case *Callable:
   175  		f, ok := v.(Callable)
   176  		if !ok {
   177  			return fmt.Errorf("got %s, want callable", v.Type())
   178  		}
   179  		*ptr = f
   180  	case *Iterable:
   181  		it, ok := v.(Iterable)
   182  		if !ok {
   183  			return fmt.Errorf("got %s, want iterable", v.Type())
   184  		}
   185  		*ptr = it
   186  	default:
   187  		// v must have type *V, where V is some subtype of exprcore.Value.
   188  		ptrv := reflect.ValueOf(ptr)
   189  		if ptrv.Kind() != reflect.Ptr {
   190  			log.Panicf("internal error: not a pointer: %T", ptr)
   191  		}
   192  		paramVar := ptrv.Elem()
   193  		if !reflect.TypeOf(v).AssignableTo(paramVar.Type()) {
   194  			// The value is not assignable to the variable.
   195  
   196  			// Detect a possible bug in the Go program that called Unpack:
   197  			// If the variable *ptr is not a subtype of Value,
   198  			// no value of v can possibly work.
   199  			if !paramVar.Type().AssignableTo(reflect.TypeOf(new(Value)).Elem()) {
   200  				log.Panicf("pointer element type does not implement Value: %T", ptr)
   201  			}
   202  
   203  			// Report exprcore dynamic type error.
   204  			//
   205  			// We prefer the exprcore Value.Type name over
   206  			// its Go reflect.Type name, but calling the
   207  			// Value.Type method on the variable is not safe
   208  			// in general. If the variable is an interface,
   209  			// the call will fail. Even if the variable has
   210  			// a concrete type, it might not be safe to call
   211  			// Type() on a zero instance. Thus we must use
   212  			// recover.
   213  
   214  			// Default to Go reflect.Type name
   215  			paramType := paramVar.Type().String()
   216  
   217  			// Attempt to call Value.Type method.
   218  			func() {
   219  				defer func() { recover() }()
   220  				paramType = paramVar.MethodByName("Type").Call(nil)[0].String()
   221  			}()
   222  			return fmt.Errorf("got %s, want %s", v.Type(), paramType)
   223  		}
   224  		paramVar.Set(reflect.ValueOf(v))
   225  	}
   226  	return nil
   227  }
   228  
   229  type intset struct {
   230  	small uint64       // bitset, used if n < 64
   231  	large map[int]bool //    set, used if n >= 64
   232  }
   233  
   234  func (is *intset) init(n int) {
   235  	if n >= 64 {
   236  		is.large = make(map[int]bool)
   237  	}
   238  }
   239  
   240  func (is *intset) set(i int) (prev bool) {
   241  	if is.large == nil {
   242  		prev = is.small&(1<<uint(i)) != 0
   243  		is.small |= 1 << uint(i)
   244  	} else {
   245  		prev = is.large[i]
   246  		is.large[i] = true
   247  	}
   248  	return
   249  }
   250  
   251  func (is *intset) get(i int) bool {
   252  	if is.large == nil {
   253  		return is.small&(1<<uint(i)) != 0
   254  	}
   255  	return is.large[i]
   256  }
   257  
   258  func (is *intset) len() int {
   259  	if is.large == nil {
   260  		// Suboptimal, but used only for error reporting.
   261  		len := 0
   262  		for i := 0; i < 64; i++ {
   263  			if is.small&(1<<uint(i)) != 0 {
   264  				len++
   265  			}
   266  		}
   267  		return len
   268  	}
   269  	return len(is.large)
   270  }