github.com/tiagovtristao/plz@v13.4.0+incompatible/src/parse/asp/interpreter.go (about)

     1  package asp
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strings"
     7  	"sync"
     8  
     9  	"github.com/thought-machine/please/src/core"
    10  )
    11  
    12  // An interpreter holds the package-independent state about our parsing process.
    13  type interpreter struct {
    14  	builtinScope *scope
    15  	scope        *scope
    16  	parser       *Parser
    17  	subincludes  map[string]pyDict
    18  	config       map[*core.Configuration]*pyConfig
    19  	mutex        sync.RWMutex
    20  	configMutex  sync.RWMutex
    21  }
    22  
    23  // newInterpreter creates and returns a new interpreter instance.
    24  // It loads all the builtin rules at this point.
    25  func newInterpreter(state *core.BuildState, p *Parser) *interpreter {
    26  	s := &scope{
    27  		state:  state,
    28  		locals: map[string]pyObject{},
    29  	}
    30  	bs := &scope{
    31  		state:  state,
    32  		locals: map[string]pyObject{},
    33  	}
    34  	i := &interpreter{
    35  		builtinScope: bs,
    36  		scope:        s,
    37  		parser:       p,
    38  		subincludes:  map[string]pyDict{},
    39  		config:       map[*core.Configuration]*pyConfig{},
    40  	}
    41  	s.interpreter = i
    42  	bs.interpreter = i
    43  	s.LoadSingletons(state)
    44  	bs.LoadSingletons(state)
    45  	return i
    46  }
    47  
    48  // LoadBuiltins loads a set of builtins from a file, optionally with its contents.
    49  func (i *interpreter) LoadBuiltins(filename string, contents []byte, statements []*Statement) error {
    50  	// Gentle hack - attach the native code once we have loaded the correct file.
    51  	// Needs to be after this file is loaded but before any of the others that will
    52  	// use functions from it.
    53  	if filename == "builtins.build_defs" || filename == "src/parse/rules/builtins.build_defs" {
    54  		defer registerBuiltins(i.builtinScope)
    55  	} else if filename == "misc_rules.build_defs" || filename == "src/parse/rules/misc_rules.build_defs" {
    56  		defer registerSubincludePackage(i.builtinScope)
    57  	} else if filename == "config_rules.build_defs" || filename == "src/parse/rules/config_rules.build_defs" {
    58  		defer setNativeCode(i.builtinScope, "select", selectFunc)
    59  	}
    60  	defer i.scope.SetAll(i.builtinScope.Freeze(), true)
    61  	if statements != nil {
    62  		return i.interpretStatements(i.builtinScope, statements)
    63  	} else if len(contents) != 0 {
    64  		return i.loadBuiltinStatements(i.parser.ParseData(contents, filename))
    65  	}
    66  	return i.loadBuiltinStatements(i.parser.parse(filename))
    67  }
    68  
    69  // loadBuiltinStatements loads statements as builtins.
    70  func (i *interpreter) loadBuiltinStatements(statements []*Statement, err error) error {
    71  	if err != nil {
    72  		return err
    73  	}
    74  	i.optimiseExpressions(reflect.ValueOf(statements))
    75  	return i.interpretStatements(i.builtinScope, i.parser.optimise(statements))
    76  }
    77  
    78  // interpretAll runs a series of statements in the context of the given package.
    79  // The first return value is for testing only.
    80  func (i *interpreter) interpretAll(pkg *core.Package, statements []*Statement) (s *scope, err error) {
    81  	s = i.scope.NewPackagedScope(pkg)
    82  	// Config needs a little separate tweaking.
    83  	// Annoyingly we'd like to not have to do this at all, but it's very hard to handle
    84  	// mutating operations like .setdefault() otherwise.
    85  	s.config = i.pkgConfig(pkg).Copy()
    86  	s.Set("CONFIG", s.config)
    87  	err = i.interpretStatements(s, statements)
    88  	if err == nil {
    89  		s.Callback = true // From here on, if anything else uses this scope, it's in a post-build callback.
    90  	}
    91  	return s, err
    92  }
    93  
    94  // interpretStatements runs a series of statements in the context of the given scope.
    95  func (i *interpreter) interpretStatements(s *scope, statements []*Statement) (err error) {
    96  	defer func() {
    97  		if r := recover(); r != nil {
    98  			if e, ok := r.(error); ok {
    99  				err = e
   100  			} else {
   101  				err = fmt.Errorf("%s", r)
   102  			}
   103  		}
   104  	}()
   105  	s.interpretStatements(statements)
   106  	return nil // Would have panicked if there was an error
   107  }
   108  
   109  // Subinclude returns the global values corresponding to subincluding the given file.
   110  func (i *interpreter) Subinclude(path string) pyDict {
   111  	i.mutex.RLock()
   112  	globals, present := i.subincludes[path]
   113  	i.mutex.RUnlock()
   114  	if present {
   115  		return globals
   116  	}
   117  	// If we get here, it's not been subincluded already. Parse it now.
   118  	// Note that there is a race here whereby it's possible for two packages to parse the same
   119  	// subinclude simultaneously - this doesn't matter since they'll get different but equivalent
   120  	// scopes, and sooner or later things will sort themselves out.
   121  	stmts, err := i.parser.parse(path)
   122  	if err != nil {
   123  		panic(err) // We're already inside another interpreter, which will handle this for us.
   124  	}
   125  	stmts = i.parser.optimise(stmts)
   126  	s := i.scope.NewScope()
   127  	// Scope needs a local version of CONFIG
   128  	s.config = i.scope.config.Copy()
   129  	s.Set("CONFIG", s.config)
   130  	i.optimiseExpressions(reflect.ValueOf(stmts))
   131  	s.interpretStatements(stmts)
   132  	locals := s.Freeze()
   133  	if s.config.overlay == nil {
   134  		delete(locals, "CONFIG") // Config doesn't have any local modifications
   135  	}
   136  	i.mutex.Lock()
   137  	defer i.mutex.Unlock()
   138  	i.subincludes[path] = locals
   139  	return s.locals
   140  }
   141  
   142  // getConfig returns a new configuration object for the given configuration object.
   143  func (i *interpreter) getConfig(config *core.Configuration) *pyConfig {
   144  	i.configMutex.RLock()
   145  	if c, present := i.config[config]; present {
   146  		i.configMutex.RUnlock()
   147  		return c
   148  	}
   149  	i.configMutex.RUnlock()
   150  	i.configMutex.Lock()
   151  	defer i.configMutex.Unlock()
   152  	c := newConfig(config)
   153  	i.config[config] = c
   154  	return c
   155  }
   156  
   157  // pkgConfig returns a new configuration object for the given package.
   158  func (i *interpreter) pkgConfig(pkg *core.Package) *pyConfig {
   159  	if pkg.Subrepo != nil && pkg.Subrepo.State != nil {
   160  		return i.getConfig(pkg.Subrepo.State.Config)
   161  	}
   162  	return i.getConfig(i.scope.state.Config)
   163  }
   164  
   165  // optimiseExpressions implements a peephole optimiser for expressions by precalculating constants
   166  // and identifying simple local variable lookups.
   167  func (i *interpreter) optimiseExpressions(v reflect.Value) {
   168  	callback := func(astStruct interface{}) interface{} {
   169  		if expr, ok := astStruct.(*Expression); ok && expr != nil {
   170  			if constant := i.scope.Constant(expr); constant != nil {
   171  				expr.Optimised = &OptimisedExpression{Constant: constant} // Extract constant expression
   172  				expr.Val = nil
   173  			} else if expr.Val != nil && expr.Val.Ident != nil && expr.Val.Call == nil && expr.Op == nil && expr.If == nil && expr.Val.Slice == nil {
   174  				if expr.Val.Property == nil && len(expr.Val.Ident.Action) == 0 {
   175  					expr.Optimised = &OptimisedExpression{Local: expr.Val.Ident.Name}
   176  				} else if expr.Val.Ident.Name == "CONFIG" && len(expr.Val.Ident.Action) == 1 && expr.Val.Ident.Action[0].Property != nil && len(expr.Val.Ident.Action[0].Property.Action) == 0 {
   177  					expr.Optimised = &OptimisedExpression{Config: expr.Val.Ident.Action[0].Property.Name}
   178  					expr.Val = nil
   179  				}
   180  			}
   181  		}
   182  		return nil
   183  	}
   184  
   185  	WalkAST(v, callback)
   186  }
   187  
   188  // A scope contains all the information about a lexical scope.
   189  type scope struct {
   190  	interpreter *interpreter
   191  	state       *core.BuildState
   192  	pkg         *core.Package
   193  	parent      *scope
   194  	locals      pyDict
   195  	config      *pyConfig
   196  	// True if this scope is for a pre- or post-build callback.
   197  	Callback bool
   198  }
   199  
   200  // NewScope creates a new child scope of this one.
   201  func (s *scope) NewScope() *scope {
   202  	return s.NewPackagedScope(s.pkg)
   203  }
   204  
   205  // NewPackagedScope creates a new child scope of this one pointing to the given package.
   206  func (s *scope) NewPackagedScope(pkg *core.Package) *scope {
   207  	s2 := &scope{
   208  		interpreter: s.interpreter,
   209  		state:       s.state,
   210  		pkg:         pkg,
   211  		parent:      s,
   212  		locals:      pyDict{},
   213  		config:      s.config,
   214  		Callback:    s.Callback,
   215  	}
   216  	if pkg != nil && pkg.Subrepo != nil && pkg.Subrepo.State != nil {
   217  		s2.state = pkg.Subrepo.State
   218  	}
   219  	return s2
   220  }
   221  
   222  // Error emits an error that stops further interpretation.
   223  // For convenience it is declared to return a pyObject but it never actually returns.
   224  func (s *scope) Error(msg string, args ...interface{}) pyObject {
   225  	panic(fmt.Errorf(msg, args...))
   226  }
   227  
   228  // Assert emits an error that stops further interpretation if the given condition is false.
   229  func (s *scope) Assert(condition bool, msg string, args ...interface{}) {
   230  	if !condition {
   231  		s.Error(msg, args...)
   232  	}
   233  }
   234  
   235  // NAssert is the inverse of Assert, it emits an error if the given condition is true.
   236  func (s *scope) NAssert(condition bool, msg string, args ...interface{}) {
   237  	if condition {
   238  		s.Error(msg, args...)
   239  	}
   240  }
   241  
   242  // Lookup looks up a variable name in this scope, walking back up its ancestor scopes as needed.
   243  // It panics if the variable is not defined.
   244  func (s *scope) Lookup(name string) pyObject {
   245  	if obj, present := s.locals[name]; present {
   246  		return obj
   247  	} else if s.parent != nil {
   248  		return s.parent.Lookup(name)
   249  	}
   250  	return s.Error("name '%s' is not defined", name)
   251  }
   252  
   253  // LocalLookup looks up a variable name in the current scope.
   254  // It does *not* walk back up parent scopes and instead returns nil if the variable could not be found.
   255  // This is typically used for things like function arguments where we're only interested in variables
   256  // in immediate scope.
   257  func (s *scope) LocalLookup(name string) pyObject {
   258  	return s.locals[name]
   259  }
   260  
   261  // Set sets the given variable in this scope.
   262  func (s *scope) Set(name string, value pyObject) {
   263  	s.locals[name] = value
   264  }
   265  
   266  // SetAll sets all contents of the given dict in this scope.
   267  // Optionally it can filter to just public objects (i.e. those not prefixed with an underscore)
   268  func (s *scope) SetAll(d pyDict, publicOnly bool) {
   269  	for k, v := range d {
   270  		if k == "CONFIG" {
   271  			// Special case; need to merge config entries rather than overwriting the entire object.
   272  			c, ok := v.(*pyFrozenConfig)
   273  			s.Assert(ok, "incoming CONFIG isn't a config object")
   274  			s.config.Merge(c)
   275  		} else if !publicOnly || k[0] != '_' {
   276  			s.locals[k] = v
   277  		}
   278  	}
   279  }
   280  
   281  // Freeze freezes the contents of this scope, preventing mutable objects from being changed.
   282  // It returns the newly frozen set of locals.
   283  func (s *scope) Freeze() pyDict {
   284  	for k, v := range s.locals {
   285  		if f, ok := v.(freezable); ok {
   286  			s.locals[k] = f.Freeze()
   287  		}
   288  	}
   289  	return s.locals
   290  }
   291  
   292  // LoadSingletons loads the global builtin singletons into this scope.
   293  func (s *scope) LoadSingletons(state *core.BuildState) {
   294  	s.Set("True", True)
   295  	s.Set("False", False)
   296  	s.Set("None", None)
   297  	if state != nil {
   298  		s.config = s.interpreter.getConfig(state.Config)
   299  		s.Set("CONFIG", s.config)
   300  	}
   301  }
   302  
   303  // interpretStatements interprets a series of statements in a particular scope.
   304  // Note that the return value is only non-nil if a return statement is encountered;
   305  // it is not implicitly the result of the last statement or anything like that.
   306  func (s *scope) interpretStatements(statements []*Statement) pyObject {
   307  	var stmt *Statement
   308  	defer func() {
   309  		if r := recover(); r != nil {
   310  			panic(AddStackFrame(stmt.Pos, r))
   311  		}
   312  	}()
   313  	for _, stmt = range statements {
   314  		if stmt.FuncDef != nil {
   315  			s.Set(stmt.FuncDef.Name, newPyFunc(s, stmt.FuncDef))
   316  		} else if stmt.If != nil {
   317  			if ret := s.interpretIf(stmt.If); ret != nil {
   318  				return ret
   319  			}
   320  		} else if stmt.For != nil {
   321  			if ret := s.interpretFor(stmt.For); ret != nil {
   322  				return ret
   323  			}
   324  		} else if stmt.Return != nil {
   325  			if len(stmt.Return.Values) == 0 {
   326  				return None
   327  			} else if len(stmt.Return.Values) == 1 {
   328  				return s.interpretExpression(stmt.Return.Values[0])
   329  			}
   330  			return pyList(s.evaluateExpressions(stmt.Return.Values))
   331  		} else if stmt.Ident != nil {
   332  			s.interpretIdentStatement(stmt.Ident)
   333  		} else if stmt.Assert != nil {
   334  			s.Assert(s.interpretExpression(stmt.Assert.Expr).IsTruthy(), stmt.Assert.Message)
   335  		} else if stmt.Raise != nil {
   336  			s.Error(s.interpretExpression(stmt.Raise).String())
   337  		} else if stmt.Literal != nil {
   338  			// Do nothing, literal statements are likely docstrings and don't require any action.
   339  		} else if stmt.Continue {
   340  			// This is definitely awkward since we need to control a for loop that's happening in a function outside this scope.
   341  			return continueIteration
   342  		} else if stmt.Pass {
   343  			continue // Nothing to do...
   344  		} else {
   345  			s.Error("Unknown statement") // Shouldn't happen, amirite?
   346  		}
   347  	}
   348  	return nil
   349  }
   350  
   351  func (s *scope) interpretIf(stmt *IfStatement) pyObject {
   352  	if s.interpretExpression(&stmt.Condition).IsTruthy() {
   353  		return s.interpretStatements(stmt.Statements)
   354  	}
   355  	for _, elif := range stmt.Elif {
   356  		if s.interpretExpression(&elif.Condition).IsTruthy() {
   357  			return s.interpretStatements(elif.Statements)
   358  		}
   359  	}
   360  	return s.interpretStatements(stmt.ElseStatements)
   361  }
   362  
   363  func (s *scope) interpretFor(stmt *ForStatement) pyObject {
   364  	for _, li := range s.iterate(&stmt.Expr) {
   365  		s.unpackNames(stmt.Names, li)
   366  		if ret := s.interpretStatements(stmt.Statements); ret != nil {
   367  			if b, ok := ret.(pyBool); ok && b == continueIteration {
   368  				continue
   369  			}
   370  			return ret
   371  		}
   372  	}
   373  	return nil
   374  }
   375  
   376  func (s *scope) interpretExpression(expr *Expression) pyObject {
   377  	// Check the optimised sites first
   378  	if expr.Optimised != nil {
   379  		if expr.Optimised.Constant != nil {
   380  			return expr.Optimised.Constant
   381  		} else if expr.Optimised.Local != "" {
   382  			return s.Lookup(expr.Optimised.Local)
   383  		}
   384  		return s.config.Property(expr.Optimised.Config)
   385  	}
   386  	defer func() {
   387  		if r := recover(); r != nil {
   388  			panic(AddStackFrame(expr.Pos, r))
   389  		}
   390  	}()
   391  	if expr.If != nil && !s.interpretExpression(expr.If.Condition).IsTruthy() {
   392  		return s.interpretExpression(expr.If.Else)
   393  	}
   394  	var obj pyObject
   395  	if expr.Val != nil {
   396  		obj = s.interpretValueExpression(expr.Val)
   397  	} else if expr.UnaryOp != nil {
   398  		obj = s.interpretValueExpression(&expr.UnaryOp.Expr)
   399  		if expr.UnaryOp.Op == "not" {
   400  			if obj.IsTruthy() {
   401  				obj = False
   402  			} else {
   403  				obj = True
   404  			}
   405  		} else {
   406  			i, ok := obj.(pyInt)
   407  			s.Assert(ok, "Unary - can only be applied to an integer")
   408  			obj = pyInt(-int(i))
   409  		}
   410  	}
   411  	for _, op := range expr.Op {
   412  		switch op.Op {
   413  		case And, Or:
   414  			// Careful here to mimic lazy-evaluation semantics (import for `x = x or []` etc)
   415  			if obj.IsTruthy() == (op.Op == And) {
   416  				obj = s.interpretExpression(op.Expr)
   417  			}
   418  		case Equal:
   419  			obj = newPyBool(reflect.DeepEqual(obj, s.interpretExpression(op.Expr)))
   420  		case NotEqual:
   421  			obj = newPyBool(!reflect.DeepEqual(obj, s.interpretExpression(op.Expr)))
   422  		case Is:
   423  			// Is only works on boolean types.
   424  			b1, isBool1 := obj.(pyBool)
   425  			b2, isBool2 := s.interpretExpression(op.Expr).(pyBool)
   426  			obj = newPyBool(isBool1 && isBool2 && b1 == b2)
   427  		case In, NotIn:
   428  			// the implementation of in is defined by the right-hand side, not the left.
   429  			obj = s.interpretExpression(op.Expr).Operator(op.Op, obj)
   430  		default:
   431  			obj = obj.Operator(op.Op, s.interpretExpression(op.Expr))
   432  		}
   433  	}
   434  	return obj
   435  }
   436  
   437  func (s *scope) interpretValueExpression(expr *ValueExpression) pyObject {
   438  	obj := s.interpretValueExpressionPart(expr)
   439  	if expr.Slice != nil {
   440  		if expr.Slice.Colon == "" {
   441  			// Indexing, much simpler...
   442  			s.Assert(expr.Slice.End == nil, "Invalid syntax")
   443  			obj = obj.Operator(Index, s.interpretExpression(expr.Slice.Start))
   444  		} else {
   445  			obj = s.interpretSlice(obj, expr.Slice)
   446  		}
   447  	}
   448  	if expr.Property != nil {
   449  		obj = s.interpretIdent(obj.Property(expr.Property.Name), expr.Property)
   450  	} else if expr.Call != nil {
   451  		obj = s.callObject("", obj, expr.Call)
   452  	}
   453  	return obj
   454  }
   455  
   456  func (s *scope) interpretValueExpressionPart(expr *ValueExpression) pyObject {
   457  	if expr.Ident != nil {
   458  		obj := s.Lookup(expr.Ident.Name)
   459  		if len(expr.Ident.Action) == 0 {
   460  			return obj // fast path
   461  		}
   462  		return s.interpretIdent(obj, expr.Ident)
   463  	} else if expr.String != "" {
   464  		// Strings are surrounded by quotes to make it easier for the parser; here they come off again.
   465  		return pyString(stringLiteral(expr.String))
   466  	} else if expr.FString != nil {
   467  		return s.interpretFString(expr.FString)
   468  	} else if expr.Int != nil {
   469  		return pyInt(expr.Int.Int)
   470  	} else if expr.Bool != "" {
   471  		return s.Lookup(expr.Bool)
   472  	} else if expr.List != nil {
   473  		return s.interpretList(expr.List)
   474  	} else if expr.Dict != nil {
   475  		return s.interpretDict(expr.Dict)
   476  	} else if expr.Tuple != nil {
   477  		// Parentheses can also indicate precedence; a single parenthesised expression does not create a list object.
   478  		l := s.interpretList(expr.Tuple)
   479  		if len(l) == 1 && expr.Tuple.Comprehension == nil {
   480  			return l[0]
   481  		}
   482  		return l
   483  	} else if expr.Lambda != nil {
   484  		// A lambda is just an inline function definition with a single return statement.
   485  		stmt := &Statement{}
   486  		stmt.Return = &ReturnStatement{
   487  			Values: []*Expression{&expr.Lambda.Expr},
   488  		}
   489  		return newPyFunc(s, &FuncDef{
   490  			Name:       "<lambda>",
   491  			Arguments:  expr.Lambda.Arguments,
   492  			Statements: []*Statement{stmt},
   493  		})
   494  	}
   495  	return None
   496  }
   497  
   498  func (s *scope) interpretFString(f *FString) pyObject {
   499  	var b strings.Builder
   500  	for _, v := range f.Vars {
   501  		b.WriteString(v.Prefix)
   502  		if v.Config != "" {
   503  			b.WriteString(s.config.MustGet(v.Config).String())
   504  		} else {
   505  			b.WriteString(s.Lookup(v.Var).String())
   506  		}
   507  	}
   508  	b.WriteString(f.Suffix)
   509  	return pyString(b.String())
   510  }
   511  
   512  func (s *scope) interpretSlice(obj pyObject, sl *Slice) pyObject {
   513  	lst, ok1 := obj.(pyList)
   514  	str, ok2 := obj.(pyString)
   515  	s.Assert(ok1 || ok2, "Unsliceable type "+obj.Type())
   516  	start := s.interpretSliceExpression(obj, sl.Start, 0)
   517  	end := s.interpretSliceExpression(obj, sl.End, pyInt(obj.Len()))
   518  	if ok1 {
   519  		return lst[start:end]
   520  	}
   521  	return str[start:end]
   522  }
   523  
   524  // interpretSliceExpression interprets one of the begin or end parts of a slice.
   525  // expr may be null, if it is the value of def is used instead.
   526  func (s *scope) interpretSliceExpression(obj pyObject, expr *Expression, def pyInt) pyInt {
   527  	if expr == nil {
   528  		return def
   529  	}
   530  	return pyIndex(obj, s.interpretExpression(expr), true)
   531  }
   532  
   533  func (s *scope) interpretIdent(obj pyObject, expr *IdentExpr) pyObject {
   534  	name := expr.Name
   535  	for _, action := range expr.Action {
   536  		if action.Property != nil {
   537  			name = action.Property.Name
   538  			obj = s.interpretIdent(obj.Property(name), action.Property)
   539  		} else if action.Call != nil {
   540  			obj = s.callObject(name, obj, action.Call)
   541  		}
   542  	}
   543  	return obj
   544  }
   545  
   546  func (s *scope) interpretIdentStatement(stmt *IdentStatement) {
   547  	if stmt.Index != nil {
   548  		// Need to special-case these, because types are immutable so we can't return a modifiable reference to them.
   549  		obj := s.Lookup(stmt.Name)
   550  		idx := s.interpretExpression(stmt.Index.Expr)
   551  		if stmt.Index.Assign != nil {
   552  			obj.IndexAssign(idx, s.interpretExpression(stmt.Index.Assign))
   553  		} else {
   554  			obj.IndexAssign(idx, obj.Operator(Index, idx).Operator(Add, s.interpretExpression(stmt.Index.AugAssign)))
   555  		}
   556  	} else if stmt.Unpack != nil {
   557  		obj := s.interpretExpression(stmt.Unpack.Expr)
   558  		l, ok := obj.(pyList)
   559  		s.Assert(ok, "Cannot unpack type %s", l.Type())
   560  		// This is a little awkward because the first item here is the name of the ident node.
   561  		s.Assert(len(l) == len(stmt.Unpack.Names)+1, "Wrong number of items to unpack; expected %d, got %d", len(stmt.Unpack.Names)+1, len(l))
   562  		s.Set(stmt.Name, l[0])
   563  		for i, name := range stmt.Unpack.Names {
   564  			s.Set(name, l[i+1])
   565  		}
   566  	} else if stmt.Action.Property != nil {
   567  		s.interpretIdent(s.Lookup(stmt.Name).Property(stmt.Action.Property.Name), stmt.Action.Property)
   568  	} else if stmt.Action.Call != nil {
   569  		s.callObject(stmt.Name, s.Lookup(stmt.Name), stmt.Action.Call)
   570  	} else if stmt.Action.Assign != nil {
   571  		s.Set(stmt.Name, s.interpretExpression(stmt.Action.Assign))
   572  	} else if stmt.Action.AugAssign != nil {
   573  		// The only augmented assignment operation we support is +=, and it's implemented
   574  		// exactly as x += y -> x = x + y since that matches the semantics of Go types.
   575  		s.Set(stmt.Name, s.Lookup(stmt.Name).Operator(Add, s.interpretExpression(stmt.Action.AugAssign)))
   576  	}
   577  }
   578  
   579  func (s *scope) interpretList(expr *List) pyList {
   580  	if expr.Comprehension == nil {
   581  		return pyList(s.evaluateExpressions(expr.Values))
   582  	}
   583  	cs := s.NewScope()
   584  	l := s.iterate(expr.Comprehension.Expr)
   585  	ret := make(pyList, 0, len(l))
   586  	cs.evaluateComprehension(l, expr.Comprehension, func(li pyObject) {
   587  		if len(expr.Values) == 1 {
   588  			ret = append(ret, cs.interpretExpression(expr.Values[0]))
   589  		} else {
   590  			ret = append(ret, pyList(cs.evaluateExpressions(expr.Values)))
   591  		}
   592  	})
   593  	return ret
   594  }
   595  
   596  func (s *scope) interpretDict(expr *Dict) pyObject {
   597  	if expr.Comprehension == nil {
   598  		d := make(pyDict, len(expr.Items))
   599  		for _, v := range expr.Items {
   600  			d.IndexAssign(s.interpretExpression(&v.Key), s.interpretExpression(&v.Value))
   601  		}
   602  		return d
   603  	}
   604  	cs := s.NewScope()
   605  	l := cs.iterate(expr.Comprehension.Expr)
   606  	ret := make(pyDict, len(l))
   607  	cs.evaluateComprehension(l, expr.Comprehension, func(li pyObject) {
   608  		ret.IndexAssign(cs.interpretExpression(&expr.Items[0].Key), cs.interpretExpression(&expr.Items[0].Value))
   609  	})
   610  	return ret
   611  }
   612  
   613  // evaluateComprehension handles iterating a comprehension's loops.
   614  // The provided callback function is called with each item to be added to the result.
   615  func (s *scope) evaluateComprehension(l pyList, comp *Comprehension, callback func(pyObject)) {
   616  	if comp.Second != nil {
   617  		for _, li := range l {
   618  			s.unpackNames(comp.Names, li)
   619  			for _, li := range s.iterate(comp.Second.Expr) {
   620  				if s.evaluateComprehensionExpression(comp, comp.Second.Names, li) {
   621  					callback(li)
   622  				}
   623  			}
   624  		}
   625  	} else {
   626  		for _, li := range l {
   627  			if s.evaluateComprehensionExpression(comp, comp.Names, li) {
   628  				callback(li)
   629  			}
   630  		}
   631  	}
   632  }
   633  
   634  // evaluateComprehensionExpression runs an expression from a list or dict comprehension, and returns true if the caller
   635  // should continue to use it, or false if it's been filtered out of the comprehension.
   636  func (s *scope) evaluateComprehensionExpression(comp *Comprehension, names []string, li pyObject) bool {
   637  	s.unpackNames(names, li)
   638  	return comp.If == nil || s.interpretExpression(comp.If).IsTruthy()
   639  }
   640  
   641  // unpackNames unpacks the given object into this scope.
   642  func (s *scope) unpackNames(names []string, obj pyObject) {
   643  	if len(names) == 1 {
   644  		s.Set(names[0], obj)
   645  	} else {
   646  		l, ok := obj.(pyList)
   647  		s.Assert(ok, "Cannot unpack %s into %s", obj.Type(), names)
   648  		s.Assert(len(l) == len(names), "Incorrect number of values to unpack; expected %d, got %d", len(names), len(l))
   649  		for i, name := range names {
   650  			s.Set(name, l[i])
   651  		}
   652  	}
   653  }
   654  
   655  // iterate returns the result of the given expression as a pyList, which is our only iterable type.
   656  func (s *scope) iterate(expr *Expression) pyList {
   657  	o := s.interpretExpression(expr)
   658  	l, ok := o.(pyList)
   659  	if !ok {
   660  		if l, ok := o.(pyFrozenList); ok {
   661  			return l.pyList
   662  		}
   663  	}
   664  	s.Assert(ok, "Non-iterable type %s; must be a list", o.Type())
   665  	return l
   666  }
   667  
   668  // evaluateExpressions runs a series of Python expressions in this scope and creates a series of concrete objects from them.
   669  func (s *scope) evaluateExpressions(exprs []*Expression) []pyObject {
   670  	l := make(pyList, len(exprs))
   671  	for i, v := range exprs {
   672  		l[i] = s.interpretExpression(v)
   673  	}
   674  	return l
   675  }
   676  
   677  // stringLiteral converts a parsed string literal (which is still surrounded by quotes) to an unquoted version.
   678  func stringLiteral(s string) string {
   679  	return s[1 : len(s)-1]
   680  }
   681  
   682  // callObject attempts to call the given object
   683  func (s *scope) callObject(name string, obj pyObject, c *Call) pyObject {
   684  	// We only allow function objects to be called, so don't bother making it part of the pyObject interface.
   685  	f, ok := obj.(*pyFunc)
   686  	if !ok {
   687  		s.Error("Non-callable object '%s' (is a %s)", name, obj.Type())
   688  	}
   689  	return f.Call(s, c)
   690  }
   691  
   692  // Constant returns an object from an expression that describes a constant,
   693  // e.g. None, "string", 42, [], etc. It returns nil if the expression cannot be determined to be constant.
   694  func (s *scope) Constant(expr *Expression) pyObject {
   695  	// Technically some of these might be constant (e.g. 'a,b,c'.split(',') or `1 if True else 2`.
   696  	// That's probably unlikely to be common though - we could do a generalised constant-folding pass
   697  	// but it's rare that people would write something of that nature in this language.
   698  	if expr.Optimised != nil && expr.Optimised.Constant != nil {
   699  		return expr.Optimised.Constant
   700  	} else if expr.Val == nil || expr.Val.Slice != nil || expr.Val.Property != nil || expr.Val.Call != nil || expr.Op != nil || expr.If != nil {
   701  		return nil
   702  	} else if expr.Val.Bool != "" || expr.Val.String != "" || expr.Val.Int != nil {
   703  		return s.interpretValueExpression(expr.Val)
   704  	} else if expr.Val.List != nil && expr.Val.List.Comprehension == nil {
   705  		// Lists can be constant if all their elements are also.
   706  		for _, v := range expr.Val.List.Values {
   707  			if s.Constant(v) == nil {
   708  				return nil
   709  			}
   710  		}
   711  		return s.interpretValueExpression(expr.Val)
   712  	}
   713  	// N.B. dicts are not optimised to constants currently because they are mutable (because Go maps have
   714  	//      pointer semantics). It might be nice to be able to do that later but it is probably not critical -
   715  	//      we might also be able to do a more aggressive pass in cases where we know we're passing a constant
   716  	//      to a builtin that won't modify it (e.g. calling build_rule with a constant dict).
   717  	return nil
   718  }
   719  
   720  // pkgFilename returns the filename of the current package, or the empty string if there is none.
   721  func (s *scope) pkgFilename() string {
   722  	if s.pkg != nil {
   723  		return s.pkg.Filename
   724  	}
   725  	return ""
   726  }