func (em *emitter) emitCallNode(call *ast.Call, goStmt bool, deferStmt bool, toFormat ast.Format) ([]int8, []reflect.Type) {
	funTi := em.ti(call.Func)
	if funTi.MethodType == methodCallInterface {
		rcvrExpr := call.Func.(*ast.Selector).Expr
		rcvrType := em.typ(rcvrExpr)
		rcvr := em.emitExpr(rcvrExpr, rcvrType)
		if kindToType(rcvrType.Kind()) != generalRegister {
			panic(internalError("not implemented"))
		}
		method := em.fb.newRegister(reflect.Func)
		name := call.Func.(*ast.Selector).Ident
		s := em.fb.makeStringValue(name)
		em.fb.emitMethodValue(s, rcvr, method, call.Func.Pos())
		call.Args = append([]ast.Expression{rcvrExpr}, call.Args...)
		stackShift := em.fb.currentStackShift()
		opts := callOptions{
			predefined:    true,
			receiverAsArg: true,
			callHasDots:   call.IsVariadic,
		}
		regs, types := em.prepareCallParameters(funTi.Type, call.Args, opts)
		if goStmt {
			em.fb.emitGo()
		}
		if deferStmt {
			panic(internalError("not implemented"))
		}
		em.fb.emitCallIndirect(method, 0, stackShift, call.Pos(), funTi.Type, toFormat)
		return regs, types
	}
	if funTi.IsNative() && !funTi.Addressable() {
		if funTi.MethodType == methodCallConcrete {
			rcv := call.Func.(*ast.Selector).Expr  
			call.Args = append([]ast.Expression{rcv}, call.Args...)
		}
		stackShift := em.fb.currentStackShift()
		opts := callOptions{
			predefined:    true,
			receiverAsArg: funTi.MethodType == methodCallConcrete,
			callHasDots:   call.IsVariadic,
		}
		regs, types := em.prepareCallParameters(funTi.Type, call.Args, opts)
		index, _ := em.fnStore.predefFunc(call.Func, true)
		if goStmt {
			em.fb.emitGo()
		}
		numVar := runtime.NoVariadicArgs
		if funTi.Type.IsVariadic() && !call.IsVariadic {
			numArgs := len(call.Args)
			if len(call.Args) == 1 {
				if callArg, ok := call.Args[0].(*ast.Call); ok {
					if numOut, ok := em.numOut(callArg); ok {
						numArgs = numOut
					}
				}
			}
			numVar = numArgs - (funTi.Type.NumIn() - 1)
		}
		if deferStmt {
			args := em.fb.currentStackShift()
			reg := em.fb.newRegister(reflect.Func)
			em.fb.emitLoadFunc(true, index, reg)
			em.fb.emitDefer(reg, int8(numVar), stackShift, args, funTi.Type)
			return regs, types
		}
		em.fb.emitCallNative(index, int8(numVar), stackShift, call.Pos())
		return regs, types
	}
	if ident, ok := call.Func.(*ast.Identifier); ok && !em.fb.declaredInFunc(ident.Name) {
		if fn, ok := em.fnStore.availableScriggoFn(em.pkg, ident.Name); ok {
			stackShift := em.fb.currentStackShift()
			regs, types := em.prepareCallParameters(fn.Type, call.Args, callOptions{callHasDots: call.IsVariadic})
			index := em.fnStore.scriggoFnIndex(fn)
			if goStmt {
				em.fb.emitGo()
			}
			if deferStmt {
				args := stackDifference(em.fb.currentStackShift(), stackShift)
				reg := em.fb.newRegister(reflect.Func)
				em.fb.emitLoadFunc(false, index, reg)
				em.fb.emitDefer(reg, runtime.NoVariadicArgs, stackShift, args, fn.Type)
				return regs, types
			}
			if fn.Macro {
				em.fb.emitCallMacro(index, stackShift, call.Pos(), toFormat)
			} else {
				em.fb.emitCallFunc(index, stackShift, call.Pos())
			}
			return regs, types
		}
	}
	if selector, ok := call.Func.(*ast.Selector); ok {
		if ident, ok := selector.Expr.(*ast.Identifier); ok {
			if fun, ok := em.fnStore.availableScriggoFn(em.pkg, ident.Name+"."+selector.Ident); ok {
				stackShift := em.fb.currentStackShift()
				regs, types := em.prepareCallParameters(fun.Type, call.Args, callOptions{callHasDots: call.IsVariadic})
				index := em.fnStore.scriggoFnIndex(fun)
				if goStmt {
					em.fb.emitGo()
				}
				if deferStmt {
					panic(internalError("not implemented"))
				}
				if fun.Macro {
					em.fb.emitCallMacro(index, stackShift, call.Pos(), toFormat)
				} else {
					em.fb.emitCallFunc(index, stackShift, call.Pos())
				}
				return regs, types
			}
		}
	}
	reg := em.emitExpr(call.Func, em.typ(call.Func))
	stackShift := em.fb.currentStackShift()
	opts := callOptions{predefined: false, callHasDots: call.IsVariadic}
	regs, types := em.prepareCallParameters(funTi.Type, call.Args, opts)
	if goStmt {
		em.fb.emitGo()
	}
	if deferStmt {
		args := stackDifference(em.fb.currentStackShift(), stackShift)
		em.fb.emitDefer(reg, int8(runtime.NoVariadicArgs), stackShift, args, funTi.Type)
		return regs, types
	}
	em.fb.emitCallIndirect(reg, int8(runtime.NoVariadicArgs), stackShift, call.Pos(), funTi.Type, toFormat)
	return regs, types
}
emitCallNode emits instructions for a function call node. It returns the
registers and the reflect types of the returned values.
goStmt indicates if the call node belongs to a 'go statement', while
deferStmt reports whether it must be deferred.
package compiler
import (
	"reflect"
	"strings"
	"github.com/open2b/scriggo/ast"
	"github.com/open2b/scriggo/internal/compiler/types"
	"github.com/open2b/scriggo/internal/runtime"
)
type emitter struct {
	fnStore  *functionStore
	varStore *varStore
	fb *functionBuilder
	labels map[*runtime.Function]map[string]label
	pkg *ast.Package
	typeInfos map[ast.Node]*typeInfo
	formatTypes map[ast.Format]reflect.Type
	isTemplate bool
	rangeLabels [][2]label
	breakable bool
	inForRange bool
	breakLabel *label
	inURL bool
	isURLSet bool
	types *types.Types
	alreadyEmittedFuncs map[*ast.Func]*runtime.Function
	alreadyInitializedVars map[*ast.Identifier]int16
	alreadyInitializedTemplatePkgs map[string]bool
}
func newEmitter(typeInfos map[ast.Node]*typeInfo, formatTypes map[ast.Format]reflect.Type, indirectVars map[*ast.Identifier]bool) *emitter {
	em := &emitter{
		labels:                         make(map[*runtime.Function]map[string]label),
		typeInfos:                      typeInfos,
		formatTypes:                    formatTypes,
		types:                          types.NewTypes(),  
		alreadyEmittedFuncs:            map[*ast.Func]*runtime.Function{},
		alreadyInitializedVars:         map[*ast.Identifier]int16{},
		alreadyInitializedTemplatePkgs: map[string]bool{},
	}
	em.fnStore = newFunctionStore(em)
	em.varStore = newVarStore(em, indirectVars)
	return em
}
func (em *emitter) ti(n ast.Node) *typeInfo {
	if ti, ok := em.typeInfos[n]; ok {
		if ti.valueType != nil {
			ti.Type = ti.valueType
		}
		return ti
	}
	return nil
}
func (em *emitter) typ(expr ast.Expression) reflect.Type {
	return em.ti(expr).Type
}
func (em *emitter) emitPackage(pkg *ast.Package, extendingFile bool, path string) (map[string]*runtime.Function, map[string]int16, []*runtime.Function) {
	if !extendingFile {
		em.pkg = pkg
	}
	inits := []*runtime.Function{}
	for _, decl := range pkg.Declarations {
		if node, ok := decl.(*ast.Import); ok {
			pkgInits := em.emitImport(node, false)
			for _, pkgInit := range pkgInits {
				add := true
				for _, ini := range inits {
					if ini == pkgInit {
						add = false
						break
					}
				}
				if add {
					inits = append(inits, pkgInits...)
				}
			}
		}
	}
	functions := map[string]*runtime.Function{}
	initToBuild := len(inits)
	if extendingFile {
	} else {
		for _, dec := range pkg.Declarations {
			if fun, ok := dec.(*ast.Func); ok {
				var fn *runtime.Function
				if emFn, ok := em.alreadyEmittedFuncs[fun]; ok {
					fn = emFn
				} else {
					if fun.Type.Macro {
						fn = newMacro("main", fun.Ident.Name, fun.Type.Reflect, fun.Format, path, fun.Pos())
					} else {
						fn = newFunction("main", fun.Ident.Name, fun.Type.Reflect, path, fun.Pos())
					}
				}
				if fun.Ident.Name == "init" {
					inits = append(inits, fn)
					continue
				}
				em.fnStore.makeAvailableScriggoFn(em.pkg, fun.Ident.Name, fn)
				isDummyMacroForRender := strings.HasPrefix(fun.Ident.Name, `M"`) && strings.HasSuffix(fun.Ident.Name, `"`)
				if isExported(fun.Ident.Name) || isDummyMacroForRender {
					functions[fun.Ident.Name] = fn
				}
			}
		}
	}
	vars := map[string]int16{}
	var initVarsFn *runtime.Function
	var initVarsFb *functionBuilder
	for _, dec := range pkg.Declarations {
		if n, ok := dec.(*ast.Var); ok {
			backupFb := em.fb
			if initVarsFn == nil {
				initVarsFn = newFunction("main", "$initvars", reflect.FuncOf(nil, nil, false), path, &ast.Position{})
				em.fnStore.makeAvailableScriggoFn(em.pkg, "$initvars", initVarsFn)
				initVarsFb = newBuilder(initVarsFn, path)
			}
			em.fb = initVarsFb
			addresses := make([]address, len(n.Lhs))
			pkgVarRegs := map[string]int8{}
			pkgVarTypes := map[string]reflect.Type{}
			for i, v := range n.Lhs {
				if isBlankIdentifier(v) {
					addresses[i] = em.addressBlankIdent(v.Pos())
					continue
				}
				if index, ok := em.alreadyInitializedVars[v]; ok {
					vars[v.Name] = index
					continue
				}
				varType := em.typ(v)
				varr := em.fb.newRegister(varType.Kind())
				addresses[i] = em.addressLocalVar(varr, varType, v.Pos(), 0)
				pkgVarRegs[v.Name] = varr
				pkgVarTypes[v.Name] = varType
				index := em.varStore.createScriggoPackageVar(em.pkg, newGlobal(pkg.Name, v.Name, varType, reflect.Value{}))
				em.alreadyInitializedVars[v] = index
				vars[v.Name] = index
			}
			em.assignValuesToAddresses(addresses, n.Rhs)
			for name, reg := range pkgVarRegs {
				index := vars[name]
				em.fb.emitSetVar(false, reg, int(index), pkgVarTypes[name].Kind())
			}
			em.fb = backupFb
		}
	}
	for _, dec := range pkg.Declarations {
		if n, ok := dec.(*ast.Func); ok {
			var fn *runtime.Function
			if isBlankIdentifier(n.Ident) {
				continue
			}
			if _, ok := em.alreadyEmittedFuncs[n]; ok {
				continue
			}
			if n.Ident.Name == "init" {
				fn = inits[initToBuild]
				initToBuild++
			} else {
				fn, _ = em.fnStore.availableScriggoFn(em.pkg, n.Ident.Name)
			}
			em.fb = newBuilder(fn, path)
			em.fb.enterScope()
			if n.Ident.Name == "main" {
				if initVarsFn != nil {
					iv, _ := em.fnStore.availableScriggoFn(em.pkg, "$initvars")
					index := em.fb.addFunction(iv)  
					em.fb.emitCallFunc(index, runtime.StackShift{}, nil)
				}
				for _, initFunc := range inits {
					index := em.fb.addFunction(initFunc)
					em.fb.emitCallFunc(index, runtime.StackShift{}, nil)
				}
			}
			em.prepareFunctionBodyParameters(n)
			em.emitNodes(n.Body.Nodes)
			em.fb.end()
			em.fb.exitScope()
			em.alreadyEmittedFuncs[n] = fn
		}
	}
	if initVarsFn != nil {
		initVarsFb.emitReturn()
		initVarsFb.end()
	}
	if initVarsFn != nil {
		inits = append(inits, initVarsFn)
	}
	return functions, vars, inits
}
type callOptions struct {
	predefined    bool
	receiverAsArg bool
	callHasDots   bool
}
func (em *emitter) prepareCallParameters(fType reflect.Type, fArgs []ast.Expression, opts callOptions) ([]int8, []reflect.Type) {
	fNumOut := fType.NumOut()
	fNumIn := fType.NumIn()
	fOutRegs := make([]int8, fNumOut)
	fOutTypes := make([]reflect.Type, fNumOut)
	for i := 0; i < fNumOut; i++ {
		t := fType.Out(i)
		fOutRegs[i] = em.fb.newRegister(t.Kind())
		fOutTypes[i] = t
	}
	if opts.receiverAsArg {
		reg := em.fb.newRegister(em.typ(fArgs[0]).Kind())
		em.fb.enterStack()
		em.emitExprR(fArgs[0], em.typ(fArgs[0]), reg)
		em.fb.exitStack()
		fArgs = fArgs[1:]
	}
	if fType.IsVariadic() {
		if em.isSpecialCall(fArgs) {
			g := fArgs[0].(*ast.Call)
			gOutCount, _ := em.numOut(g)  
			nonVarArgsCount := fNumIn - 1
			varArgsCount := gOutCount - (fNumIn - 1)
			var nonVarParamRegs []int8
			for i := 0; i < nonVarArgsCount; i++ {
				reg := em.fb.newRegister(fType.In(i).Kind())
				nonVarParamRegs = append(nonVarParamRegs, reg)
			}
			var varParamRegs []int8
			sliceType := fType.In(fNumIn - 1)
			if opts.predefined {
				for i := 0; i < varArgsCount; i++ {
					reg := em.fb.newRegister(sliceType.Elem().Kind())
					varParamRegs = append(varParamRegs, reg)
				}
			} else {
				varParamRegs = []int8{em.fb.newRegister(reflect.Slice)}
			}
			em.fb.enterStack()
			gOutRegs, gOutTypes := em.emitCallNode(g, false, false, runtime.ReturnString)
			for i := 0; i < nonVarArgsCount; i++ {
				dstType := fType.In(i)
				reg := nonVarParamRegs[i]
				em.changeRegister(false, gOutRegs[i], reg, gOutTypes[i], dstType)
			}
			if opts.predefined {
				for i := range varParamRegs {
					em.changeRegister(false, gOutRegs[nonVarArgsCount+i], varParamRegs[i], gOutTypes[nonVarArgsCount+i], sliceType.Elem())
				}
			} else {
				slice := varParamRegs[0]
				if varArgsCount == 0 {
					c := em.fb.makeGeneralValue(reflect.Zero(sliceType))
					em.changeRegister(true, c, slice, sliceType, sliceType)
				} else {
					pos := fArgs[0].Pos()
					em.fb.emitMakeSlice(true, true, sliceType, int8(varArgsCount), int8(varArgsCount), slice, pos)
					for i := nonVarArgsCount; i < len(gOutRegs); i++ {
						gArgReg := gOutRegs[i]
						gArgType := gOutTypes[i]
						index := em.fb.newRegister(reflect.Int)
						em.changeRegister(true, int8(i-nonVarArgsCount), index, intType, intType)
						if canEmitDirectly(gArgType.Kind(), sliceType.Elem().Kind()) {
							em.fb.emitSetSlice(false, slice, gArgReg, index, pos, sliceType.Elem().Kind())
						} else {
							tmp := em.fb.newRegister(sliceType.Elem().Kind())
							em.changeRegister(false, gArgReg, tmp, gArgType, sliceType.Elem())
							em.fb.emitSetSlice(false, slice, tmp, index, pos, sliceType.Elem().Kind())
						}
					}
				}
			}
			em.fb.exitStack()
			return fOutRegs, fOutTypes
		}
		for i := 0; i < fNumIn-1; i++ {
			t := fType.In(i)
			reg := em.fb.newRegister(t.Kind())
			em.fb.enterStack()
			em.emitExprR(fArgs[i], t, reg)
			em.fb.exitStack()
		}
		if opts.callHasDots {
			slice := fArgs[len(fArgs)-1]
			sliceType := fType.In(fType.NumIn() - 1)
			reg := em.fb.newRegister(sliceType.Kind())
			em.fb.enterStack()
			em.emitExprR(slice, sliceType, reg)
			em.fb.exitStack()
			return fOutRegs, fOutTypes
		}
		varArgsCount := len(fArgs) - (fNumIn - 1)
		t := fType.In(fNumIn - 1).Elem()
		if opts.predefined {
			for i := 0; i < varArgsCount; i++ {
				reg := em.fb.newRegister(t.Kind())
				em.fb.enterStack()
				em.emitExprR(fArgs[i+fNumIn-1], t, reg)
				em.fb.exitStack()
			}
		} else {
			slice := em.fb.newRegister(reflect.Slice)
			em.fb.emitMakeSlice(true, true, fType.In(fNumIn-1), int8(varArgsCount), int8(varArgsCount), slice, nil)  
			for i := 0; i < varArgsCount; i++ {
				tmp := em.fb.newRegister(t.Kind())
				em.fb.enterStack()
				em.emitExprR(fArgs[i+fNumIn-1], t, tmp)
				em.fb.exitStack()
				index := em.fb.newRegister(reflect.Int)
				em.fb.emitMove(true, int8(i), index, reflect.Int)
				pos := fArgs[len(fArgs)-1].Pos()
				em.fb.emitSetSlice(false, slice, tmp, index, pos, fType.In(fNumIn-1).Elem().Kind())
			}
		}
		return fOutRegs, fOutTypes
	}
	if fNumIn > 1 && len(fArgs) == 1 {
		gOutRegs, gOutTypes := em.emitCallNode(fArgs[0].(*ast.Call), false, false, runtime.ReturnString)
		for i := range gOutRegs {
			dstType := fType.In(i)
			reg := em.fb.newRegister(dstType.Kind())
			em.changeRegister(false, gOutRegs[i], reg, gOutTypes[i], dstType)
		}
		return fOutRegs, fOutTypes
	}
	for i := 0; i < fNumIn; i++ {
		t := fType.In(i)
		reg := em.fb.newRegister(t.Kind())
		em.fb.enterStack()
		em.emitExprR(fArgs[i], t, reg)
		em.fb.exitStack()
	}
	return fOutRegs, fOutTypes
}
func (em *emitter) prepareFunctionBodyParameters(fn *ast.Func) {
	for _, out := range fn.Type.Result {
		kind := em.typ(out.Type).Kind()
		reg := em.fb.newRegister(kind)
		if out.Ident != nil && !isBlankIdentifier(out.Ident) {
			em.fb.bindVarReg(out.Ident.Name, reg)
		}
	}
	for i, inParam := range fn.Type.Parameters {
		kind := em.typ(inParam.Type).Kind()
		if fn.Type.IsVariadic && i == len(fn.Type.Parameters)-1 {
			kind = reflect.Slice
		}
		if inParam.Ident == nil || isBlankIdentifier(inParam.Ident) {
			_ = em.fb.newRegister(kind)
		} else {
			arg := em.fb.newRegister(kind)
			em.fb.bindVarReg(inParam.Ident.Name, arg)
		}
	}
	for _, out := range fn.Type.Result {
		if out.Ident != nil && em.varStore.mustBeDeclaredAsIndirect(out.Ident) {
			dst := em.fb.scopeLookup(out.Ident.Name)
			reg := em.fb.newIndirectRegister()
			em.fb.emitNew(em.typ(out.Type), -reg)
			em.fb.bindVarReg(out.Ident.Name, reg)
			em.fb.fn.FinalRegs = append(em.fb.fn.FinalRegs, [2]int8{-reg, dst})
		}
	}
	for _, param := range fn.Type.Parameters {
		if em.varStore.mustBeDeclaredAsIndirect(param.Ident) {
			reg := em.fb.scopeLookup(param.Ident.Name)
			indirect := em.fb.newIndirectRegister()
			typ := em.typ(param.Type)
			em.fb.emitNew(typ, -indirect)
			em.changeRegister(false, reg, indirect, typ, typ)
			em.fb.bindVarReg(param.Ident.Name, indirect)
		}
	}
}