code: func (v *evalVisitor) evalProgram(program *ast.Program, ctx interface{}, data *DataFrame, key interface{}) string {
  	blockParams := make(map[string]interface{})
  
  	// compute block params
  	if len(program.BlockParams) > 0 {
  		blockParams[program.BlockParams[0]] = ctx
  	}
  
  	if (len(program.BlockParams) > 1) && (key != nil) {
  		blockParams[program.BlockParams[1]] = key
  	}
  
  	// push contexts
  	if len(blockParams) > 0 {
  		v.pushBlockParams(blockParams)
  	}
  
  	ctxVal := reflect.ValueOf(ctx)
  	if ctxVal.IsValid() {
  		v.pushCtx(ctxVal)
  	}
  
  	if data != nil {
  		v.setDataFrame(data)
  	}
  
  	// evaluate program
  	result, _ := program.Accept(v).(string)
  
  	// pop contexts
  	if data != nil {
  		v.popDataFrame()
  	}
  
  	if ctxVal.IsValid() {
  		v.popCtx()
  	}
  
  	if len(blockParams) > 0 {
  		v.popBlockParams()
  	}
  
  	return result
  }
docs: //
  // Evaluation
  //
  // evalProgram eEvaluates program with given context and returns string result