code: func newplan(e *expr, context Context, stable bool, reqs []Req) (GraphiteFunc, []Req, error) {
  	if e.etype != etFunc && e.etype != etName {
  		return nil, nil, errors.New("request must be a function call or metric pattern")
  	}
  	if e.etype == etName {
  		req := NewReq(e.str, context.from, context.to, context.consol)
  		reqs = append(reqs, req)
  		return NewGet(req), reqs, nil
  	} else if e.etype == etFunc && e.str == "seriesByTag" {
  		// `seriesByTag` function requires resolving expressions to series
  		// (similar to path expressions handled above). Since we need the
  		// arguments of seriesByTag to do the resolution, we store the function
  		// string back into the Query member of a new request to be parsed later.
  		// TODO - find a way to prevent this parse/encode/parse/encode loop
  		expressionStr := "seriesByTag(" + e.argsStr + ")"
  		req := NewReq(expressionStr, context.from, context.to, context.consol)
  		reqs = append(reqs, req)
  		return NewGet(req), reqs, nil
  	}
  	// here e.type is guaranteed to be etFunc
  	fdef, ok := funcs[e.str]
  	if !ok {
  		return nil, nil, ErrUnknownFunction(e.str)
  	}
  	if stable && !fdef.stable {
  		return nil, nil, ErrUnknownFunction(e.str)
  	}
  
  	fn := fdef.constr()
  	reqs, err := newplanFunc(e, fn, context, stable, reqs)
  	return fn, reqs, err
  }
docs: // newplan adds requests as needed for the given expr, resolving function calls as needed