func compileRangeStmt(ctx *blockCtx, v *ast.RangeStmt) {
	cb := ctx.cb
	comments := cb.Comments()
	if v.Tok == token.DEFINE {
		names := make([]string, 1, 2)
		if v.Key == nil {
			names[0] = "_"
		} else {
			names[0] = v.Key.(*ast.Ident).Name
		}
		if v.Value != nil {
			names = append(names, v.Value.(*ast.Ident).Name)
		}
		cb.ForRange(names...)
		compileExpr(ctx, v.X)
	} else {
		cb.ForRange()
		n := 0
		if v.Key == nil {
			if v.Value != nil {
				ctx.cb.VarRef(nil)  
				n++
			}
		} else {
			compileExprLHS(ctx, v.Key)
			n++
		}
		if v.Value != nil {
			compileExprLHS(ctx, v.Value)
			n++
		}
		compileExpr(ctx, v.X)
	}
	pos := v.TokPos
	if pos == 0 {
		pos = v.For
	}
	cb.RangeAssignThen(pos)
	compileStmts(ctx, v.Body.List)
	cb.SetComments(comments, true)
	setBodyHandler(ctx)
	cb.End()
}
forRange(names...) x rangeAssignThen
body
end
forRange k v x rangeAssignThen
body
end
package cl
import (
	"fmt"
	"log"
	"path/filepath"
	"reflect"
	goast "go/ast"
	gotoken "go/token"
	"go/types"
	"github.com/goplus/gop/ast"
	"github.com/goplus/gop/token"
	"github.com/goplus/gox"
)
func relFile(dir string, file string) string {
	if rel, err := filepath.Rel(dir, file); err == nil {
		if rel[0] == '.' {
			return rel
		}
		return "./" + rel
	}
	return file
}
func commentStmt(ctx *blockCtx, stmt ast.Stmt) {
	if ctx.fileLine {
		start := stmt.Pos()
		pos := ctx.fset.Position(start)
		if ctx.relativePath {
			pos.Filename = relFile(ctx.targetDir, pos.Filename)
		}
		line := fmt.Sprintf("\n//line %s:%d", pos.Filename, pos.Line)
		comments := &goast.CommentGroup{
			List: []*goast.Comment{{Text: line}},
		}
		ctx.cb.SetComments(comments, false)
	}
}
func compileStmts(ctx *blockCtx, body []ast.Stmt) {
	for _, stmt := range body {
		if v, ok := stmt.(*ast.LabeledStmt); ok {
			l := v.Label
			ctx.cb.NewLabel(l.Pos(), l.Name)
		}
	}
	for _, stmt := range body {
		compileStmt(ctx, stmt)
	}
}
func compileStmt(ctx *blockCtx, stmt ast.Stmt) {
	if enableRecover {
		defer func() {
			if e := recover(); e != nil {
				ctx.handleRecover(e)
				ctx.cb.ResetStmt()
			}
		}()
	}
	commentStmt(ctx, stmt)
	switch v := stmt.(type) {
	case *ast.ExprStmt:
		compileExpr(ctx, v.X)
		if canAutoCall(v.X) && isFunc(ctx.cb.InternalStack().Get(-1).Type) {
			ctx.cb.Call(0)
		}
	case *ast.AssignStmt:
		compileAssignStmt(ctx, v)
	case *ast.ReturnStmt:
		compileReturnStmt(ctx, v)
	case *ast.IfStmt:
		compileIfStmt(ctx, v)
	case *ast.SwitchStmt:
		compileSwitchStmt(ctx, v)
	case *ast.RangeStmt:
		compileRangeStmt(ctx, v)
	case *ast.ForStmt:
		compileForStmt(ctx, v)
	case *ast.ForPhraseStmt:
		compileForPhraseStmt(ctx, v)
	case *ast.IncDecStmt:
		compileIncDecStmt(ctx, v)
	case *ast.DeferStmt:
		compileDeferStmt(ctx, v)
	case *ast.GoStmt:
		compileGoStmt(ctx, v)
	case *ast.DeclStmt:
		compileDeclStmt(ctx, v)
	case *ast.TypeSwitchStmt:
		compileTypeSwitchStmt(ctx, v)
	case *ast.SendStmt:
		compileSendStmt(ctx, v)
	case *ast.BranchStmt:
		compileBranchStmt(ctx, v)
	case *ast.LabeledStmt:
		compileLabeledStmt(ctx, v)
	case *ast.BlockStmt:
		ctx.cb.Block()
		compileStmts(ctx, v.List)
		ctx.cb.End()
		return
	case *ast.SelectStmt:
		compileSelectStmt(ctx, v)
	case *ast.EmptyStmt:
	default:
		log.Panicln("TODO - compileStmt failed: unknown -", reflect.TypeOf(v))
	}
	ctx.cb.EndStmt()
}
func canAutoCall(x ast.Expr) bool {
retry:
	switch t := x.(type) {
	case *ast.Ident:
		return true
	case *ast.SelectorExpr:
		x = t.X
		goto retry
	}
	return false
}
func compileReturnStmt(ctx *blockCtx, expr *ast.ReturnStmt) {
	var n = -1
	var results *types.Tuple
	for i, ret := range expr.Results {
		if c, ok := ret.(*ast.CompositeLit); ok && c.Type == nil {
			if n < 0 {
				results = ctx.cb.Func().Type().(*types.Signature).Results()
				n = results.Len()
			}
			var typ types.Type
			if i < n {
				typ = results.At(i).Type()
			}
			compileCompositeLit(ctx, c, typ, true)
		} else {
			twoValue := false
			if len(expr.Results) == 1 {
				if _, ok := ret.(*ast.ComprehensionExpr); ok {
					results = ctx.cb.Func().Type().(*types.Signature).Results()
					twoValue = (results.Len() == 2)
				}
			}
			compileExpr(ctx, ret, twoValue)
		}
	}
	ctx.cb.Return(len(expr.Results), expr)
}
func compileIncDecStmt(ctx *blockCtx, expr *ast.IncDecStmt) {
	compileExprLHS(ctx, expr.X)
	ctx.cb.IncDec(gotoken.Token(expr.Tok))
}
func compileSendStmt(ctx *blockCtx, expr *ast.SendStmt) {
	compileExpr(ctx, expr.Chan)
	compileExpr(ctx, expr.Value)
	ctx.cb.Send()
}
func compileAssignStmt(ctx *blockCtx, expr *ast.AssignStmt) {
	tok := expr.Tok
	twoValue := (len(expr.Lhs) == 2 && len(expr.Rhs) == 1)
	if tok == token.DEFINE {
		names := make([]string, len(expr.Lhs))
		for i, lhs := range expr.Lhs {
			if v, ok := lhs.(*ast.Ident); ok {
				names[i] = v.Name
			} else {
				log.Panicln("TODO: non-name $v on left side of :=")
			}
		}
		ctx.cb.DefineVarStart(expr.Pos(), names...)
		if enableRecover {
			defer func() {
				if e := recover(); e != nil {
					ctx.cb.ResetInit()
					panic(e)
				}
			}()
		}
		for _, rhs := range expr.Rhs {
			compileExpr(ctx, rhs, twoValue)
		}
		ctx.cb.EndInit(len(expr.Rhs))
		return
	}
	for _, lhs := range expr.Lhs {
		compileExprLHS(ctx, lhs)
	}
	for _, rhs := range expr.Rhs {
		compileExpr(ctx, rhs, twoValue)
	}
	if tok == token.ASSIGN {
		ctx.cb.AssignWith(len(expr.Lhs), len(expr.Rhs), expr)
		return
	}
	if len(expr.Lhs) != 1 || len(expr.Rhs) != 1 {
		panic("TODO: invalid syntax of assign by operator")
	}
	ctx.cb.AssignOp(gotoken.Token(tok), expr)
}