code: func NewPlan(exprs []*expr, from, to, mdp uint32, stable bool, reqs []Req) (Plan, error) {
  	var err error
  	var funcs []GraphiteFunc
  	for _, e := range exprs {
  		var fn GraphiteFunc
  		context := Context{
  			from: from,
  			to:   to,
  		}
  		fn, reqs, err = newplan(e, context, stable, reqs)
  		if err != nil {
  			return Plan{}, err
  		}
  		funcs = append(funcs, fn)
  	}
  	return Plan{
  		Reqs:          reqs,
  		exprs:         exprs,
  		funcs:         funcs,
  		MaxDataPoints: mdp,
  		From:          from,
  		To:            to,
  	}, nil
  }
docs: // Plan validates the expressions and comes up with the initial (potentially non-optimal) execution plan
  // which is just a list of requests and the expressions.
  // traverse tree and as we go down:
  // * make sure function exists
  // * validation of arguments
  // * allow functions to modify the Context (change data range or consolidation)
  // * future version: allow functions to mark safe to pre-aggregate using consolidateBy or not