func (em *emitter) emitSelector(v *ast.Selector, reg int8, dstType reflect.Type) {
	ti := em.ti(v)
	if ti.MethodType == methodValueConcrete || ti.MethodType == methodValueInterface {
		expr := v.Expr
		typ := em.typ(expr)
		rcvr := em.emitExpr(expr, typ)
		if kindToType(typ.Kind()) != generalRegister {
			oldRcvr := rcvr
			rcvr = em.fb.newRegister(reflect.Interface)
			em.fb.emitTypify(false, typ, oldRcvr, rcvr)
		}
		if kindToType(dstType.Kind()) == generalRegister {
			s := em.fb.makeStringValue(v.Ident)
			em.fb.emitMethodValue(s, rcvr, reg, v.Pos())
		} else {
			panic(internalError("not implemented"))
		}
		return
	}
	if index, ok := em.varStore.nonLocalVarIndex(v); ok {
		if reg == 0 {
			return
		}
		if canEmitDirectly(ti.Type.Kind(), dstType.Kind()) {
			em.fb.emitGetVar(int(index), reg, dstType.Kind())
			return
		}
		tmp := em.fb.newRegister(ti.Type.Kind())
		em.fb.emitGetVar(int(index), tmp, ti.Type.Kind())
		em.changeRegister(false, tmp, reg, ti.Type, dstType)
		return
	}
	if ident, ok := v.Expr.(*ast.Identifier); ok {
		if sf, ok := em.fnStore.availableScriggoFn(em.pkg, ident.Name+"."+v.Ident); ok {
			if reg == 0 {
				return
			}
			index := em.fnStore.scriggoFnIndex(sf)
			em.fb.emitLoadFunc(false, index, reg)
			em.changeRegister(false, reg, reg, em.typ(v), dstType)
			return
		}
	}
	expr := v.Expr
	if op, ok := expr.(*ast.UnaryOperator); ok && op.Op == ast.OperatorPointer {
		expr = op.Expr
	}
	typ := em.typ(expr)
	exprReg := em.emitExpr(expr, typ)
	var field reflect.StructField
	if typ.Kind() == reflect.Ptr {
		field, _ = typ.Elem().FieldByName(v.Ident)
	} else {
		field, _ = typ.FieldByName(v.Ident)
	}
	index := em.fb.makeFieldIndex(field.Index)
	if canEmitDirectly(field.Type.Kind(), dstType.Kind()) {
		em.fb.emitField(exprReg, index, reg, dstType.Kind())
		return
	}
	tmp := em.fb.newRegister(field.Type.Kind())
	em.fb.emitField(exprReg, index, tmp, field.Type.Kind())
	em.changeRegister(false, tmp, reg, field.Type, dstType)
}
emitSelector emits selector in register reg.
package compiler
import (
	"fmt"
	"math"
	"reflect"
	"github.com/open2b/scriggo/ast"
	"github.com/open2b/scriggo/internal/runtime"
)
func (em *emitter) emitExpr(expr ast.Expression, dstType reflect.Type) int8 {
	reg, _ := em._emitExpr(expr, dstType, 0, false, false)
	return reg
}
func (em *emitter) emitExprK(expr ast.Expression, dstType reflect.Type) (int8, bool) {
	return em._emitExpr(expr, dstType, 0, false, true)
}
func (em *emitter) emitExprR(expr ast.Expression, dstType reflect.Type, reg int8) {
	_, _ = em._emitExpr(expr, dstType, reg, true, false)
}
func (em *emitter) _emitExpr(expr ast.Expression, dstType reflect.Type, reg int8, useGivenReg bool, allowK bool) (int8, bool) {
	ti := em.ti(expr)
	if !useGivenReg {
		if allowK && ti.HasValue() && !ti.IsNative() {
			switch v := ti.value.(type) {
			case int64:
				if canEmitDirectly(reflect.Int, dstType.Kind()) {
					if -128 <= v && v <= 127 {
						return int8(v), true
					}
				}
			case float64:
				if canEmitDirectly(reflect.Float64, dstType.Kind()) {
					if math.Floor(v) == v && -128 <= v && v <= 127 {
						return int8(v), true
					}
				}
			}
		}
		if expr, ok := expr.(*ast.Identifier); ok && em.fb.declaredInFunc(expr.Name) {
			if canEmitDirectly(ti.Type.Kind(), dstType.Kind()) {
				return em.fb.scopeLookup(expr.Name), false
			}
		}
		reg = em.fb.newRegister(dstType.Kind())
	}
	if ti != nil && ti.HasValue() && !ti.IsNative() {
		return em.emitValueNotPredefined(ti, reg, dstType)
	}
	if index, ok := em.fnStore.predefFunc(expr, false); ok {
		em.fb.emitLoadFunc(true, index, reg)
		em.changeRegister(false, reg, reg, ti.Type, dstType)
		return reg, false
	}
	switch expr := expr.(type) {
	case *ast.BinaryOperator:
		em.emitBinaryOp(expr, reg, dstType)
		return reg, false
	case *ast.Call:
		if em.ti(expr.Func).IsBuiltinFunction() {
			em.emitBuiltin(expr, reg, dstType)
			return reg, false
		}
		if ti := em.ti(expr.Func); ti.IsType() {
			convertType := em.typ(expr.Func)
			if reg == 0 {
				return reg, false
			}
			typ := em.typ(expr.Args[0])
			arg := em.emitExpr(expr.Args[0], typ)
			markdownToHTML := ti.IsFormatType() && ti.Type != stringType
			if canEmitDirectly(convertType.Kind(), dstType.Kind()) {
				if markdownToHTML {
					em.changeRegisterConvertFormat(false, arg, reg, typ, convertType)
				} else {
					em.changeRegister(false, arg, reg, typ, convertType)
				}
				return reg, false
			}
			em.fb.enterStack()
			tmp := em.fb.newRegister(convertType.Kind())
			if markdownToHTML {
				em.changeRegisterConvertFormat(false, arg, tmp, typ, convertType)
			} else {
				em.changeRegister(false, arg, tmp, typ, convertType)
			}
			em.changeRegister(false, tmp, reg, convertType, dstType)
			em.fb.exitStack()
			return reg, false
		}
		em.fb.enterStack()
		regs, types := em.emitCallNode(expr, false, false, runtime.ReturnString)
		if reg != 0 {
			em.changeRegister(false, regs[0], reg, types[0], dstType)
		}
		em.fb.exitStack()
	case *ast.CompositeLiteral:
		return em.emitCompositeLiteral(expr, reg, dstType)
	case *ast.DollarIdentifier:
		return em._emitExpr(expr.IR.Ident, dstType, reg, useGivenReg, allowK)
	case *ast.Default:
		ex := expr.Expr1
		if ti := em.ti(expr.Expr1); ti == nil {
			ex = expr.Expr2
		}
		return em._emitExpr(ex, dstType, reg, useGivenReg, allowK)
	case *ast.TypeAssertion:
		exprType := em.typ(expr.Expr)
		exprReg := em.emitExpr(expr.Expr, exprType)
		assertType := em.typ(expr.Type)
		pos := expr.Pos()
		if canEmitDirectly(assertType.Kind(), dstType.Kind()) {
			em.fb.emitAssert(exprReg, assertType, reg)
			em.fb.emitPanic(0, exprType, pos)
			return reg, false
		}
		em.fb.enterScope()
		tmp := em.fb.newRegister(assertType.Kind())
		em.fb.emitAssert(exprReg, assertType, tmp)
		em.fb.emitPanic(0, exprType, pos)
		em.changeRegister(false, tmp, reg, assertType, dstType)
		em.fb.exitScope()
	case *ast.Selector:
		em.emitSelector(expr, reg, dstType)
	case *ast.UnaryOperator:
		em.emitUnaryOp(expr, reg, dstType)
		return reg, false
	case *ast.Func:
		if reg == 0 {
			return reg, false
		}
		var tmp int8
		if canEmitDirectly(reflect.Func, dstType.Kind()) {
			tmp = reg
		} else {
			tmp = em.fb.newRegister(reflect.Func)
		}
		fn := &runtime.Function{
			Pkg:    em.fb.fn.Pkg,
			File:   em.fb.fn.File,
			Macro:  expr.Type.Macro,
			Format: expr.Format,
			Pos:    convertPosition(expr.Pos()),
			Type:   ti.Type,
			Parent: em.fb.fn,
		}
		em.fb.emitLoadFunc(false, em.fb.addFunction(fn), tmp)
		em.setFunctionVarRefs(fn, expr.Upvars)
		funcLitBuilder := newBuilder(fn, em.fb.getPath())
		currFB := em.fb
		em.fb = funcLitBuilder
		em.fb.enterScope()
		em.prepareFunctionBodyParameters(expr)
		em.emitNodes(expr.Body.Nodes)
		em.fb.exitScope()
		em.fb.end()
		em.fb = currFB
		em.changeRegister(false, tmp, reg, ti.Type, dstType)
	case *ast.Identifier:
		if reg == 0 {
			return reg, false
		}
		typ := ti.Type
		if em.fb.declaredInFunc(expr.Name) {
			ident := em.fb.scopeLookup(expr.Name)
			em.changeRegister(false, ident, reg, typ, dstType)
			return reg, false
		}
		if index, ok := em.varStore.nonLocalVarIndex(expr); ok {
			if canEmitDirectly(typ.Kind(), dstType.Kind()) {
				em.fb.emitGetVar(index, reg, dstType.Kind())
				return reg, false
			}
			em.fb.enterStack()
			tmp := em.fb.newRegister(typ.Kind())
			em.fb.emitGetVar(index, tmp, typ.Kind())
			em.changeRegister(false, tmp, reg, typ, dstType)
			em.fb.exitStack()
			return reg, false
		}
		if fun, ok := em.fnStore.availableScriggoFn(em.pkg, expr.Name); ok {
			em.fb.emitLoadFunc(false, em.fnStore.scriggoFnIndex(fun), reg)
			em.changeRegister(false, reg, reg, ti.Type, dstType)
			return reg, false
		}
		panic(internalError("none of the previous conditions matched identifier %v", expr))
	case *ast.Index:
		exprType := em.typ(expr.Expr)
		exprReg := em.emitExpr(expr.Expr, exprType)
		var indexType reflect.Type
		if exprType.Kind() == reflect.Map {
			indexType = exprType.Key()
		} else {
			indexType = intType
		}
		index, kindex := em.emitExprK(expr.Index, indexType)
		var elemType reflect.Type
		if exprType.Kind() == reflect.String {
			elemType = uint8Type
		} else {
			elemType = exprType.Elem()
		}
		pos := expr.Pos()
		if canEmitDirectly(elemType.Kind(), dstType.Kind()) {
			em.fb.emitIndex(kindex, exprReg, index, reg, exprType, pos, true)
			return reg, false
		}
		em.fb.enterStack()
		tmp := em.fb.newRegister(elemType.Kind())
		em.fb.emitIndex(kindex, exprReg, index, tmp, exprType, pos, true)
		em.changeRegister(false, tmp, reg, elemType, dstType)
		em.fb.exitStack()
	case *ast.Render:
		em.emitNodes([]ast.Node{expr.IR.Import})
		return em._emitExpr(expr.IR.Call, dstType, reg, useGivenReg, allowK)
	case *ast.Slicing:
		exprType := em.typ(expr.Expr)
		src := em.emitExpr(expr.Expr, exprType)
		var low, high int8 = 0, -1
		var kLow, kHigh = true, true
		if expr.Low != nil {
			low, kLow = em.emitExprK(expr.Low, em.typ(expr.Low))
		}
		if expr.High != nil {
			high, kHigh = em.emitExprK(expr.High, em.typ(expr.High))
		}
		pos := expr.Pos()
		if exprType.Kind() == reflect.String {
			if canEmitDirectly(reflect.String, dstType.Kind()) {
				em.fb.emitStringSlice(kLow, kHigh, src, reg, low, high, pos)
			} else {
				em.fb.enterStack()
				tmp := em.fb.newRegister(reflect.String)
				em.fb.emitStringSlice(kLow, kHigh, src, tmp, low, high, pos)
				em.changeRegister(false, tmp, reg, exprType, dstType)
				em.fb.exitStack()
			}
		} else {
			var max int8 = -1
			var kMax = true
			if expr.Max != nil {
				max, kMax = em.emitExprK(expr.Max, em.typ(expr.Max))
			}
			if canEmitDirectly(exprType.Kind(), dstType.Kind()) {
				em.fb.emitSlice(kLow, kHigh, kMax, src, reg, low, high, max, pos)
			} else {
				em.fb.enterStack()
				tmpType := exprType
				if tmpType.Kind() == reflect.Array {
					tmpType = reflect.SliceOf(tmpType.Elem())
				}
				tmp := em.fb.newRegister(tmpType.Kind())
				em.fb.emitSlice(kLow, kHigh, kMax, src, tmp, low, high, max, pos)
				em.changeRegister(false, tmp, reg, tmpType, dstType)
				em.fb.exitStack()
			}
		}
	default:
		panic(fmt.Sprintf("scriggo/emitter: unexpected node type %T (expr: %s)", expr, expr))
	}
	return reg, false
}
func (em *emitter) emitBinaryOp(expr *ast.BinaryOperator, reg int8, regType reflect.Type) {
	var (
		ti   = em.ti(expr)
		typ  = ti.Type
		kind = typ.Kind()
		op   = expr.Operator()
		pos  = expr.Pos()
	)
	if kind == reflect.Complex64 || kind == reflect.Complex128 {
		em.emitComplexOperation(typ, expr.Expr1, op, expr.Expr2, reg, regType)
		return
	}
	if op == ast.OperatorAnd || op == ast.OperatorOr {
		x := reg
		y := int8(0)
		direct := canEmitDirectly(regType.Kind(), reflect.Bool)
		if !direct {
			em.fb.enterStack()
			x = em.fb.newRegister(reflect.Bool)
		}
		if op == ast.OperatorAnd {
			y = 1
		}
		em.emitExprR(expr.Expr1, boolType, x)
		endIf := em.fb.newLabel()
		em.fb.emitIf(true, x, runtime.ConditionEqual, y, reflect.Int, pos)
		em.fb.emitGoto(endIf)
		em.emitExprR(expr.Expr2, boolType, x)
		em.fb.setLabelAddr(endIf)
		if !direct {
			em.changeRegister(false, x, reg, boolType, regType)
			em.fb.exitStack()
		}
		return
	}
	if em.ti(expr.Expr1).Nil() || em.ti(expr.Expr2).Nil() {
		em.changeRegister(true, 1, reg, boolType, regType)
		em.emitCondition(expr)
		em.changeRegister(true, 0, reg, boolType, regType)
		return
	}
	t1 := em.typ(expr.Expr1)
	t2 := em.typ(expr.Expr2)
	x := em.emitExpr(expr.Expr1, t1)
	y, ky := em.emitExprK(expr.Expr2, t2)
	if reg == 0 {
		return
	}
	if op == ast.OperatorAddition && t1.Kind() == reflect.String {
		if ky {
			y = em.emitExpr(expr.Expr2, t2)
		}
		if canEmitDirectly(kind, regType.Kind()) {
			em.fb.emitConcat(x, y, reg)
			return
		}
		em.fb.enterStack()
		tmp := em.fb.newRegister(kind)
		em.fb.emitConcat(x, y, tmp)
		em.changeRegister(false, tmp, reg, typ, regType)
		em.fb.exitStack()
		return
	}
	switch op {
	case ast.OperatorBitAnd, ast.OperatorBitOr, ast.OperatorXor, ast.OperatorAndNot:
		z := reg
		direct := canEmitDirectly(kind, regType.Kind())
		if !direct {
			em.fb.enterStack()
			z = em.fb.newRegister(kind)
		}
		switch op {
		case ast.OperatorBitAnd:
			em.fb.emitAnd(ky, x, y, z, kind)
		case ast.OperatorBitOr:
			em.fb.emitOr(ky, x, y, z, kind)
		case ast.OperatorXor:
			em.fb.emitXor(ky, x, y, z, kind)
		case ast.OperatorAndNot:
			em.fb.emitAndNot(ky, x, y, z, kind)
		}
		if !direct {
			em.changeRegister(false, z, reg, typ, regType)
			em.fb.exitStack()
		}
		return
	}
	if ast.OperatorAddition <= op && op <= ast.OperatorModulo ||
		op == ast.OperatorLeftShift || op == ast.OperatorRightShift {
		if kind == reflect.Int {
			z := reg
			direct := canEmitDirectly(kind, regType.Kind())
			if !direct {
				em.fb.enterStack()
				z = em.fb.newRegister(kind)
			}
			switch op {
			case ast.OperatorAddition:
				em.fb.emitAdd(ky, x, y, z, kind)
			case ast.OperatorSubtraction:
				em.fb.emitSub(ky, x, y, z, kind)
			case ast.OperatorMultiplication:
				em.fb.emitMul(ky, x, y, z, kind)
			case ast.OperatorDivision:
				em.fb.emitDiv(ky, x, y, z, kind, pos)
			case ast.OperatorModulo:
				em.fb.emitRem(ky, x, y, z, kind, pos)
			case ast.OperatorLeftShift:
				em.fb.emitShl(ky, x, y, z, kind)
			case ast.OperatorRightShift:
				em.fb.emitShr(ky, x, y, z, kind)
			}
			if !direct {
				em.changeRegister(false, z, reg, typ, regType)
				em.fb.exitStack()
			}
			return
		}
		em.fb.enterStack()
		z := em.fb.newRegister(kind)
		em.changeRegister(false, x, z, typ, typ)
		switch op {
		case ast.OperatorAddition:
			em.fb.emitAdd(ky, z, y, z, kind)
		case ast.OperatorSubtraction:
			em.fb.emitSub(ky, z, y, z, kind)
		case ast.OperatorMultiplication:
			em.fb.emitMul(ky, z, y, z, kind)
		case ast.OperatorDivision:
			em.fb.emitDiv(ky, z, y, z, kind, pos)
		case ast.OperatorModulo:
			em.fb.emitRem(ky, z, y, z, kind, pos)
		case ast.OperatorLeftShift:
			em.fb.emitShl(ky, z, y, z, kind)
		case ast.OperatorRightShift:
			em.fb.emitShr(ky, z, y, z, kind)
		}
		em.changeRegister(false, z, reg, typ, regType)
		em.fb.exitStack()
		return
	}
	if op == ast.OperatorContains || op == ast.OperatorNotContains {
		not := op == ast.OperatorNotContains
		z := reg
		directly := canEmitDirectly(reflect.Bool, regType.Kind())
		if !directly {
			em.fb.enterStack()
			z = em.fb.newRegister(reflect.Bool)
		}
		em.fb.emitMove(true, 1, z, reflect.Bool)
		em.emitContains(not, ky, x, y, t1, t2, pos)
		em.fb.emitMove(true, 0, z, reflect.Bool)
		if !directly {
			em.changeRegister(false, z, reg, typ, regType)
			em.fb.exitStack()
		}
		return
	}
	z := reg
	directly := canEmitDirectly(kind, regType.Kind())
	if !directly {
		em.fb.enterStack()
		z = em.fb.newRegister(kind)
	}
	em.fb.emitMove(true, 1, z, reflect.Bool)
	em.emitComparison(op, ky, x, y, t1, t2, pos)
	em.fb.emitMove(true, 0, z, reflect.Bool)
	if !directly {
		em.changeRegister(false, z, reg, typ, regType)
		em.fb.exitStack()
	}
}
func (em *emitter) emitCompositeLiteral(expr *ast.CompositeLiteral, reg int8, dstType reflect.Type) (int8, bool) {
	typ := em.typ(expr.Type)
	switch typ.Kind() {
	case reflect.Slice, reflect.Array:
		if reg == 0 {
			for _, kv := range expr.KeyValues {
				typ := em.typ(kv.Value)
				em.emitExprR(kv.Value, typ, 0)
			}
			return reg, false
		}
		em.fb.enterScope()
		workingReg := reg
		if !canEmitDirectly(typ.Kind(), dstType.Kind()) {
			workingReg = em.fb.newRegister(typ.Kind())
		}
		if typ.Kind() == reflect.Slice {
			length := em.compositeLiteralLen(expr)
			k := length <= 127
			if !k {
				r := em.fb.newRegister(reflect.Int)
				em.fb.emitLoad(em.fb.makeIntValue(int64(length)), r, reflect.Int)
				length = int(r)
			}
			em.fb.emitMakeSlice(k, k, typ, int8(length), int8(length), workingReg, expr.Pos())
		} else {
			em.fb.emitMakeArray(typ, workingReg)
		}
		elemKind := typ.Elem().Kind()
		var index int64 = -1
		for _, kv := range expr.KeyValues {
			if kv.Key == nil {
				index++
			} else {
				index = em.ti(kv.Key).Constant.int64()
			}
			if ti := em.ti(kv.Value); ti.HasValue() {
				var isZero bool
				switch elemKind {
				case reflect.Interface, reflect.Func:
					isZero = ti.value == nil
				case reflect.Ptr, reflect.Slice, reflect.Map, reflect.Chan:
					isZero = reflect.ValueOf(ti.value).IsNil()
				default:
					isZero = ti.IsConstant() && ti.Constant.zero()
				}
				if isZero {
					continue
				}
			}
			em.fb.enterStack()
			indexReg := em.fb.newRegister(reflect.Int)
			if index > 127 {
				em.fb.emitLoad(em.fb.makeIntValue(index), indexReg, reflect.Int)
			} else {
				em.fb.emitMove(true, int8(index), indexReg, reflect.Int)
			}
			elem, k := em.emitExprK(kv.Value, typ.Elem())
			if workingReg != 0 {
				em.fb.emitSetSlice(k, workingReg, elem, indexReg, expr.Pos(), elemKind)
			}
			em.fb.exitStack()
		}
		if !canEmitDirectly(typ.Kind(), dstType.Kind()) {
			em.changeRegister(false, workingReg, reg, typ, dstType)
		}
		em.fb.exitScope()
	case reflect.Struct:
		if reg == 0 {
			for _, kv := range expr.KeyValues {
				em.emitExprR(kv.Value, em.typ(kv.Value), 0)
			}
			return reg, false
		}
		if len(expr.KeyValues) == 0 {
			if canEmitDirectly(typ.Kind(), dstType.Kind()) {
				em.fb.emitMakeStruct(typ, reg)
				return reg, false
			}
			em.fb.enterStack()
			tmp := em.fb.newRegister(typ.Kind())
			em.fb.emitMakeStruct(typ, tmp)
			em.changeRegister(false, tmp, reg, typ, dstType)
			em.fb.exitStack()
		}
		em.fb.enterStack()
		var structt int8
		if canEmitDirectly(typ.Kind(), dstType.Kind()) {
			structt = em.fb.newRegister(reflect.Struct)
		} else {
			structt = reg
		}
		em.fb.emitMakeStruct(typ, structt)
		for _, kv := range expr.KeyValues {
			name := kv.Key.(*ast.Identifier).Name
			field, _ := typ.FieldByName(name)
			valueType := em.typ(kv.Value)
			if canEmitDirectly(valueType.Kind(), field.Type.Kind()) {
				value, k := em.emitExprK(kv.Value, valueType)
				index := em.fb.makeFieldIndex(field.Index)
				em.fb.emitSetField(k, structt, index, value, field.Type.Kind())
			} else {
				em.fb.enterStack()
				tmp := em.emitExpr(kv.Value, valueType)
				value := em.fb.newRegister(field.Type.Kind())
				em.changeRegister(false, tmp, value, valueType, field.Type)
				em.fb.exitStack()
				index := em.fb.makeFieldIndex(field.Index)
				em.fb.emitSetField(false, structt, index, value, field.Type.Kind())
			}
		}
		em.changeRegister(false, structt, reg, typ, dstType)
		em.fb.exitStack()
	case reflect.Map:
		if reg == 0 {
			for _, kv := range expr.KeyValues {
				em.emitExprR(kv.Value, em.typ(kv.Value), 0)
			}
			return reg, false
		}
		tmp := em.fb.newRegister(reflect.Map)
		size := len(expr.KeyValues)
		if size <= 127 {
			em.fb.emitMakeMap(typ, true, int8(size), tmp)
		} else {
			index := em.fb.makeIntValue(int64(size))
			sizeReg := em.fb.newRegister(reflect.Int)
			em.fb.emitLoad(index, sizeReg, reflect.Int)
			em.fb.emitMakeMap(typ, false, sizeReg, tmp)
		}
		for _, kv := range expr.KeyValues {
			em.fb.enterStack()
			key := em.fb.newRegister(typ.Key().Kind())
			em.emitExprR(kv.Key, typ.Key(), key)
			value, k := em.emitExprK(kv.Value, typ.Elem())
			em.fb.emitSetMap(k, tmp, value, key, typ, expr.Pos())
			em.fb.exitStack()
		}
		em.changeRegister(false, tmp, reg, typ, dstType)
	}
	return reg, false
}