<commit_before>// Copyright 2017 <NAME>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package compiler

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/token"
	"go/types"
	"io"
	"reflect"
	"strings"
)

type CppGen struct {
	fset *token.FileSet
	ast  *ast.File

	pkg *types.Package
	inf types.Info

	initPkgs []string

	input  io.Reader
	output io.Writer

	recvs VarStack

	idents int

	curVarType types.Type

	symbolFilter *SymbolFilter
}

type VarStack struct {
	vars  []*types.Var
	count int
}

func (s *VarStack) Push(v *types.Var) {
	s.vars = append(s.vars[:s.count], v)
	s.count++
}

func (s *VarStack) Pop() *types.Var {
	if s.count == 0 {
		return nil
	}
	s.count--
	return s.vars[s.count]
}

func (s *VarStack) Curr() *types.Var { return s.vars[s.count-1] }

func (s *VarStack) Lookup(name string) *types.Var {
	for cur := s.count - 1; cur >= 0; cur-- {
		if v := s.vars[cur]; v != nil && name == v.Name() {
			return v
		}
	}
	return nil
}

type basicTypeInfo struct {
	nilVal string
	typ    string
}

var basicTypeToCpp map[types.BasicKind]basicTypeInfo
var goTypeToBasic map[string]types.BasicKind

func init() {
	basicTypeToCpp = map[types.BasicKind]basicTypeInfo{
		types.Bool:          {"false", "bool"},
		types.UntypedBool:   {"false", "bool"},
		types.Int:           {"0", "int"},
		types.UntypedInt:    {"0", "int"},
		types.Int8:          {"0", "int8_t"},
		types.Int16:         {"0", "int16_t"},
		types.Int32:         {"0", "int32_t"},
		types.Int64:         {"0", "int64_t"},
		types.Uint:          {"0", "unsigned int"},
		types.Uint8:         {"0", "uint8_t"},
		types.Uint16:        {"0", "uint16_t"},
		types.Uint32:        {"0", "uint32_t"},
		types.Uint64:        {"0", "uint64_t"},
		types.Uintptr:       {"0", "uintptr_t"},
		types.Float32:       {"0", "float"},
		types.UntypedFloat:  {"0", "float"},
		types.Float64:       {"0", "double"},
		types.String:        {"\"\"", "std::string"},
		types.UntypedString: {"\"\"", "std::string"},
		types.UnsafePointer: {"std::nullptr", "void*"},
		types.Complex128:    {"0, 0", "moku::complex<double>"},
		types.Complex64:     {"0, 0", "moku::complex<float>"},
		types.UntypedRune:   {"0", "uint32_t"},
	}
	goTypeToBasic = map[string]types.BasicKind{
		"bool":       types.Bool,
		"int":        types.Int,
		"int8":       types.Int8,
		"int16":      types.Int16,
		"int32":      types.Int32,
		"int64":      types.Int64,
		"uint":       types.Uint,
		"uint8":      types.Uint8,
		"uint16":     types.Uint16,
		"uint32":     types.Uint32,
		"uint64":     types.Uint64,
		"uintptr":    types.Uintptr,
		"float32":    types.Float32,
		"float64":    types.Float64,
		"string":     types.String,
		"complex128": types.Complex128,
		"complex64":  types.Complex64,
	}
}

func (c *CppGen) newIdent() (ret string) {
	ret = fmt.Sprintf("_ident_%d_", c.idents)
	c.idents++
	return
}

func (c *CppGen) toTypeSig(t types.Type) (string, error) {
	f := func(t types.Type) (string, error) {
		switch typ := t.(type) {
		default:
			return "", fmt.Errorf("Unknown type: %s", reflect.TypeOf(typ))

		case *types.Chan:
			elemTyp, err := c.toTypeSig(typ.Elem())
			if err != nil {
				return "", nil
			}

			var dirMod string
			switch typ.Dir() {
			case types.SendRecv:
				dirMod = "true, true"
			case types.SendOnly:
				dirMod = "true, false"
			case types.RecvOnly:
				dirMod = "false, true"
			}

			return fmt.Sprintf("moku::channel<%s, %s>", elemTyp, dirMod), nil

		case *types.Map:
			k, err := c.toTypeSig(typ.Key())
			if err != nil {
				return "", err
			}

			v, err := c.toTypeSig(typ.Elem())
			if err != nil {
				return "", err
			}

			return fmt.Sprintf("std::map<%s, %s>", k, v), nil

		case *types.Slice:
			s, err := c.toTypeSig(typ.Elem())
			if err != nil {
				return "", err
			}

			return fmt.Sprintf("moku::slice<%s>", s), nil

		case *types.Array:
			s, err := c.toTypeSig(typ.Elem())
			if err != nil {
				return "", err
			}

			return fmt.Sprintf("std::vector<%s>", s), nil

		case *types.Pointer:
			s, err := c.toTypeSig(typ.Elem())
			if err != nil {
				return "", err
			}

			return fmt.Sprintf("%s*", s), nil

		case *types.Interface:
			if typ.Empty() {
				return "moku::empty_interface", nil
			}

			return c.toTypeSig(typ.Underlying())

		case *types.Named:
			switch typ.Obj().Name() {
			case "error":
				return "moku::error", nil
			default:
				return typ.Obj().Name(), nil
			}

		case *types.Basic:
			if v, ok := basicTypeToCpp[typ.Kind()]; ok {
				return v.typ, nil
			}

			return "", fmt.Errorf("Unsupported basic type: %s", typ)

		case *types.Tuple:
			var r []string

			items := typ.Len()
			for i := 0; i < items; i++ {
				s, err := c.toTypeSig(typ.At(i).Type())
				if err != nil {
					return "", err
				}

				r = append(r, s)
			}

			return strings.Join(r, ", "), nil

		case *types.Signature:
			var retType []string
			if r := typ.Results(); r != nil {
				s, err := c.toTypeSig(r)
				if err != nil {
					return "", err
				}
				retType = append(retType, s)
			} else {
				retType = append(retType, "void")
			}

			var paramTypes []string
			if p := typ.Params(); p != nil {
				s, err := c.toTypeSig(p)
				if err != nil {
					return "", err
				}
				paramTypes = append(paramTypes, s)
			}

			p := strings.Join(paramTypes, ", ")
			if len(retType) == 1 {
				r := retType[0]
				return fmt.Sprintf("std::function<%s(%s)>", r, p), nil
			}

			r := strings.Join(retType, ", ")
			return fmt.Sprintf("std::function<std::tuple<%s>(%s)>", r, p), nil
		}
	}

	sig, err := f(t)
	if err != nil {
		return sig, err
	}

	return sig, nil
}

func (c *CppGen) toNilVal(t types.Type) (string, error) {
	f := func(t types.Type) (string, error) {
		switch typ := t.(type) {
		case *types.Basic:
			if v, ok := basicTypeToCpp[typ.Kind()]; ok {
				return v.nilVal, nil
			}
		case *types.Pointer, *types.Signature:
			return "std::nullptr", nil

		case *types.Slice, *types.Map, *types.Chan,
			*types.Interface, *types.Named, *types.Array,
			*types.Struct:

			return "", nil
		}

		err := fmt.Errorf("Unknown nil value for type %s", reflect.TypeOf(t))
		return "", err
	}

	nilVal, err := f(t)
	if err != nil {
		return nilVal, err
	}

	if types.IsInterface(t) {
		return "", nil
	}

	return nilVal, err
}

func (c *CppGen) genFuncProto(name string, sig *types.Signature, out func(name, retType, params string) error) (err error) {
	sigParm := sig.Params()
	var params []string
	for p := 0; p < sigParm.Len(); p++ {
		parm := sigParm.At(p)
		typ, err := c.toTypeSig(parm.Type())
		if err != nil {
			return err
		}

		params = append(params, fmt.Sprintf("%s %s", typ, parm.Name()))
	}

	res := sig.Results()
	var retType string
	switch res.Len() {
	case 0:
		retType = "void"
	case 1:
		s, err := c.toTypeSig(res.At(0).Type())
		if err != nil {
			return err
		}
		retType = s
	default:
		var mult []string

		for r := 0; r < res.Len(); r++ {
			s, err := c.toTypeSig(res.At(r).Type())
			if err != nil {
				return err
			}

			mult = append(mult, s)
		}

		retType = fmt.Sprintf("std::tuple<%s>", strings.Join(mult, ", "))
	}

	return out(name, retType, strings.Join(params, ", "))
}

func (c *CppGen) genInterface(name string, iface *types.Interface) (err error) {
	fmt.Fprintf(c.output, "struct %s {\n", name)

	for m := iface.NumMethods(); m > 0; m-- {
		meth := iface.Method(m - 1)
		sig := meth.Type().(*types.Signature)

		err = c.genFuncProto(meth.Name(), sig, func(name, retType, params string) error {
			fmt.Fprintf(c.output, "virtual %s %s(%s) = 0;\n", retType, name, params)
			return nil
		})
		if err != nil {
			return err
		}
	}

	fmt.Fprintf(c.output, "};\n")
	return err
}

func (c *CppGen) genIfaceForType(n *types.Named, out func(ifaces []string) error) (err error) {
	// FIXME: this is highly inneficient and won't scale at all
	ifaces := make(map[string]struct{})
	ifaceMeths := make(map[string]struct{})
	for k, v := range c.inf.Types {
		if _, ok := k.(*ast.InterfaceType); !ok {
			continue
		}

		iface := v.Type.(*types.Interface)
		if !types.Implements(n, iface) {
			continue
		}

		for _, typ := range c.inf.Defs {
			if def, ok := typ.(*types.TypeName); ok {
				if !types.IsInterface(def.Type()) {
					continue
				}
				if !types.Implements(def.Type(), iface) {
					continue
				}

				for i := 0; i < iface.NumMethods(); i++ {
					ifaceMeths[iface.Method(i).Name()] = struct{}{}
				}

				derived := fmt.Sprintf("public %s", def.Name())
				ifaces[derived] = struct{}{}
				break
			}
		}
	}

	uniqIfaces := make([]string, 0, len(ifaces))
	for k := range ifaces {
		uniqIfaces = append(uniqIfaces, k)
	}
	if err = out(uniqIfaces); err != nil {
		return err
	}

	mset := types.NewMethodSet(n)
	for i := 0; i < mset.Len(); i++ {
		sel := mset.At(i)
		switch sel.Kind() {
		default:
			return fmt.Errorf("Kind %d not supported here", sel.Kind())

		case types.MethodVal:
			f := sel.Obj().(*types.Func)
			sig := f.Type().(*types.Signature)

			err = c.genFuncProto(f.Name(), sig, func(name, retType, params string) error {
				if _, virtual := ifaceMeths[f.Name()]; virtual {
					fmt.Fprintf(c.output, "virtual %s %s(%s) override;\n", retType, name, params)
				} else {
					fmt.Fprintf(c.output, "%s %s(%s);\n", retType, name, params)
				}
				return nil
			})
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func (c *CppGen) genStruct(name string, s *types.Struct, n *types.Named) (err error) {
	fmt.Fprintf(c.output, "struct %s", name)
	defer fmt.Fprintf(c.output, "};\n")

	return c.genIfaceForType(n, func(ifaces []string) error {
		if ifaces != nil {
			fmt.Fprintf(c.output, " : %s", strings.Join(ifaces, ", "))
		}

		fmt.Fprint(c.output, " {\n")
		numFields := s.NumFields()
		var nilCmp []string
		for f := 0; f < numFields; f++ {
			f := s.Field(f)

			typ, err := c.toTypeSig(f.Type())
			if err != nil {
				return fmt.Errorf("Couldn't generate field: %s", err)
			}

			nilVal, err := c.toNilVal(f.Type())
			if err != nil {
				return fmt.Errorf("Couldn't determine nil value for %s: %s", name, err)
			}

			if nilVal != "" {
				nilCmp = append(nilCmp, fmt.Sprintf("%s == %s", f.Name(), nilVal))

				fmt.Fprintf(c.output, "%s %s{%s};\n", typ, f.Name(), nilVal)
				continue
			}

			nilCmp = append(nilCmp, fmt.Sprintf("%s._isNil_()", f.Name()))
			fmt.Fprintf(c.output, "%s %s;\n", typ, f.Name())
		}

		fmt.Fprintf(c.output, "bool _isNil_() const { return %s; }", strings.Join(nilCmp, " && "))
		return nil
	})
}

func (c *CppGen) genBasicType(name string, b *types.Basic, n *types.Named) (err error) {
	fmt.Fprintf(c.output, "struct %s", name)
	defer fmt.Fprintf(c.output, "};\n")

	return c.genIfaceForType(n, func(ifaces []string) error {
		typ, err := c.toTypeSig(b.Underlying())
		if err != nil {
			return fmt.Errorf("Could not determine underlying type: %s", err)
		}

		nilValue, err := c.toNilVal(b.Underlying())
		if err != nil {
			return fmt.Errorf("Could not determine nil value for type %s: %s", typ, err)
		}

		base := []string{fmt.Sprintf("moku::basic<%s>", typ)}
		for _, iface := range ifaces {
			base = append(base, iface)
		}

		fmt.Fprintf(c.output, ": %s {\n", strings.Join(base, ", "))
		fmt.Fprintf(c.output, "%s() : moku::basic<%s>{%s} {}\n", name, typ, nilValue)
		fmt.Fprintf(c.output, "bool _isNil_() const { return %s(this) == %s; }", typ, nilValue)

		return nil
	})
}

func (c *CppGen) genNamedType(name string, n *types.Named) (err error) {
	switch t := n.Underlying().(type) {
	default:
		return fmt.Errorf("What to do with the named type %v?", reflect.TypeOf(t))

	case *types.Interface:
		return c.genInterface(name, t)

	case *types.Struct:
		return c.genStruct(name, t, n)

	case *types.Basic:
		return c.genBasicType(name, t, n)
	}
}

func (c *CppGen) genPrototype(name string, sig *types.Signature) error {
	return c.genFuncProto(name, sig, func(name, retType, params string) error {
		fmt.Fprintf(c.output, "%s %s(%s);\n", retType, name, params)
		return nil
	})
}

func (c *CppGen) genVar(gen *nodeGen, v *types.Var, mainBlock bool) error {
	typ, err := c.toTypeSig(v.Type())
	if err != nil {
		return fmt.Errorf("Couldn't get type signature for variable: %s", err)
	}

	nilVal, err := c.toNilVal(v.Type())
	if err != nil {
		return fmt.Errorf("Couldn't get nil value for variable: %s", err)
	}

	switch {
	case mainBlock:
		if !v.Exported() {
			fmt.Fprint(gen.out, "static ")
		}
		fmt.Fprintf(gen.out, "%s %s;\n", typ, v.Name())
	case gen.escapees[v]:
		println(v, "escapes")
		fmt.Fprintf(gen.out, "%s %s{%s}; /* escapes */\n", typ, v.Name(), nilVal)
	default:
		fmt.Fprintf(gen.out, "%s %s{%s};\n", typ, v.Name(), nilVal)
	}

	return nil
}

func (c *CppGen) genConst(gen *nodeGen, k *types.Const, mainBlock bool) error {
	typ, err := c.toTypeSig(k.Type())
	if err != nil {
		return fmt.Errorf("Couldn't get type signature for variable: %s", err)
	}

	if mainBlock {
		if !k.Exported() {
			fmt.Fprint(gen.out, "static ")
		}
		fmt.Fprintf(gen.out, "constexpr %s %s{%s};\n", typ, k.Name(), k.Val())
	} else {
		fmt.Fprintf(gen.out, "constexpr %s %s{%s};\n", typ, k.Name(), k.Val())
	}

	return nil
}

func (c *CppGen) genNamespace(p *types.Package) (err error) {
	s := p.Scope()

	if c.symbolFilter.Once(s, "#pragma once") {
		fmt.Fprintln(c.output, "#pragma once")
	}

	for _, imp := range p.Imports() {
		include := fmt.Sprintf("#include \"%s.h\"", imp.Name())
		if c.symbolFilter.Once(s, include) {
			fmt.Fprintln(c.output, include)
		}
	}

	if len(s.Names()) == 0 {
		return nil
	}

	if c.symbolFilter.Once(s, "namespace "+p.Name()) {
		fmt.Fprintf(c.output, "namespace %s {\n", p.Name())
		defer fmt.Fprintf(c.output, "} // namespace %s\n", p.Name())
	}

	genTypeProto := func(name string, obj types.Object) error {
		switch t := obj.Type().(type) {
		default:
			return nil

		case *types.Named:
			return c.genNamedType(name, t)

		case *types.Signature:
			return c.genPrototype(name, t)
		}
	}

	for _, name := range s.Names() {
		obj := s.Lookup(name)

		if !c.symbolFilter.Once(s, name) {
			continue
		}

		if name == "main" {
			name = "_main"
		}

		switch t := obj.(type) {
		case *types.Func:
			if t.Name() == "init" {
				c.initPkgs = append(c.initPkgs, p.Name())
			}
			if err = genTypeProto(name, obj); err != nil {
				return err
			}
		case *types.TypeName:
			if err = genTypeProto(name, obj); err != nil {
				return err
			}
		case *types.Var:
			gen := nodeGen{out: c.output}
			if err = c.genVar(&gen, t, true); err != nil {
				return err
			}
		case *types.Const:
			gen := nodeGen{out: c.output}
			if err = c.genConst(&gen, t, true); err != nil {
				return err
			}
		default:
			return fmt.Errorf("Don't know how to generate: %s", reflect.TypeOf(t))
		}
	}

	return nil
}

func (c *CppGen) genMapType(m *ast.MapType) (string, error) {
	k, err := c.genExpr(m.Key)
	if err != nil {
		return "", err
	}
	v, err := c.genExpr(m.Value)
	if err != nil {
		return "", err
	}

	return fmt.Sprintf("std::map<%s, %s>", k, v), nil
}

func (c *CppGen) genCallExpr(ce *ast.CallExpr) (string, error) {
	fun, err := c.genExpr(ce.Fun)
	if err != nil {
		return "", err
	}

	var args []string
	for _, arg := range ce.Args {
		argExp, err := c.genExpr(arg)
		if err != nil {
			return "", err
		}

		args = append(args, argExp)
	}
	if ce.Ellipsis.IsValid() {
		// TODO
	}

	return fmt.Sprintf("%s(%s)", fun, strings.Join(args, ", ")), nil
}

func (c *CppGen) genBasicLit(b *ast.BasicLit) (string, error) {
	switch b.Kind {
	default:
		return "", fmt.Errorf("Unknown basic literal type: %+v", b)

	case token.INT, token.FLOAT, token.CHAR, token.STRING:
		return b.Value, nil

	case token.IMAG:
		return "", fmt.Errorf("Imaginary numbers not supported")
	}
}

func (c *CppGen) genIdent(i *ast.Ident) (string, error) {
	if this := c.recvs.Lookup(i.Name); this != nil {
		return "this", nil
	}
	if basicTyp, ok := goTypeToBasic[i.Name]; ok {
		return basicTypeToCpp[basicTyp].typ, nil
	}
	return i.Name, nil
}

func (c *CppGen) genStarExpr(s *ast.StarExpr) (string, error) {
	return c.genUnaryExpr(&ast.UnaryExpr{X: s.X, Op: token.MUL})
}

func (c *CppGen) genKeyValueExpr(kv *ast.KeyValueExpr) (string, error) {
	key, err := c.genExpr(kv.Key)
	if err != nil {
		return "", err
	}
	val, err := c.genExpr(kv.Value)
	if err != nil {
		return "", err
	}

	switch c.curVarType.(type) {
	default:
		return fmt.Sprintf("{%s, %s}", key, val), nil

	case *types.Named:
		return fmt.Sprintf("%s: %s", key, val), nil
	}
}

func (c *CppGen) genExpr(x ast.Expr) (string, error) {
	switch x := x.(type) {
	default:
		return "", fmt.Errorf("Couldn't generate expression with type: %s", reflect.TypeOf(x))

	case *ast.KeyValueExpr:
		return c.genKeyValueExpr(x)

	case *ast.StarExpr:
		return c.genStarExpr(x)

	case *ast.FuncLit:
		return c.genFuncLit(x)

	case *ast.CompositeLit:
		return c.genCompositeLit(x)

	case *ast.BinaryExpr:
		return c.genBinaryExpr(x)

	case *ast.CallExpr:
		return c.genCallExpr(x)

	case *ast.SelectorExpr:
		return c.genSelectorExpr(x)

	case *ast.ParenExpr:
		return c.genParenExpr(x)

	case *ast.SliceExpr:
		return c.genSliceExpr(x)

	case *ast.IndexExpr:
		return c.genIndexExpr(x)

	case *ast.UnaryExpr:
		return c.genUnaryExpr(x)

	case *ast.ArrayType:
		return c.genArrayType(x)

	case *ast.MapType:
		return c.genMapType(x)

	case *ast.BasicLit:
		return c.genBasicLit(x)

	case *ast.Ident:
		return c.genIdent(x)
	}
}

func (c *CppGen) genInit() bool {
	for ident, _ := range c.inf.Defs {
		if ident.Name == "init" {
			fmt.Fprintf(c.output, "void init();\n")
			return true
		}
	}

	return false
}

func (c *CppGen) genMain() (err error) {
	hasInit := c.genInit()

	fmt.Fprintf(c.output, "int main() {\n")

	for _, pkg := range c.initPkgs {
		fmt.Fprintf(c.output, "%s::init();\n", pkg)
	}

	if hasInit {
		fmt.Fprintf(c.output, "init();\n")
	}

	for _, init := range c.inf.InitOrder {
		if len(init.Lhs) == 1 {
			fmt.Fprintf(c.output, "%s", init.Lhs[0].Name())
		} else {
			var tie []string

			for _, lhs := range init.Lhs {
				tie = append(tie, lhs.Name())
			}

			fmt.Fprintf(c.output, "std::tie(%s)", strings.Join(tie, ", "))
		}

		expr, err := c.genExpr(init.Rhs)
		if err != nil {
			return fmt.Errorf("Couldn't write initialization code: %s", err)
		}

		fmt.Fprintf(c.output, "= %s;\n", expr)
	}

	fmt.Fprintf(c.output, "_main();\n")
	fmt.Fprintf(c.output, "return 0;\n")
	fmt.Fprintf(c.output, "}\n")

	return nil
}

type nodeGen struct {
	out      io.Writer
	hasDefer bool

	// SwitchStmt generation
	labels         []string
	curLbl, defLbl int

	escapees map[*types.Var]bool
}

func (c *CppGen) genComment(gen *nodeGen, comment *ast.Comment) error {
	fmt.Fprintf(gen.out, "/* %s */", comment.Text)
	return nil
}

func (c *CppGen) genFuncDecl(gen *nodeGen, f *ast.FuncDecl) (err error) {
	var typ types.Object
	typ, ok := c.inf.Defs[f.Name]
	if !ok {
		return fmt.Errorf("Could not find type for func %s", f.Name.Name)
	}

	name := f.Name.Name
	if name == "main" {
		name = "_main"
	}

	fun := typ.(*types.Func)
	sig := fun.Type().(*types.Signature)
	recv := sig.Recv()
	err = c.genFuncProto(name, sig, func(name, retType, params string) (err error) {
		if recv != nil {
			var typ string
			switch t := recv.Type().(type) {
			case *types.Named:
				typ = t.Obj().Name()
			case *types.Pointer:
				if typ, err = c.toTypeSig(t.Elem()); err != nil {
					return err
				}
			}
			name = fmt.Sprintf("%s::%s", typ, name)
		}
		fmt.Fprintf(gen.out, "%s %s(%s)\n", retType, name, params)
		return nil
	})
	if err != nil {
		return err
	}

	c.recvs.Push(recv)
	defer c.recvs.Pop()

	filt := func(name string) bool {
		if recv != nil && recv.Name() == name {
			return false
		}

		parms := sig.Params()
		for p := 0; p < parms.Len(); p++ {
			if parms.At(p).Name() == name {
				return false
			}
		}

		return true
	}
	if err = c.genScopeAndBody(gen, f.Body, f.Type, true, filt); err != nil {
		return err
	}

	return err
}

func (c *CppGen) genAssignStmt(gen *nodeGen, a *ast.AssignStmt) (err error) {
	var varTypes []types.Type
	var vars []string

	defer func() { c.curVarType = nil }()

	for _, e := range a.Lhs {
		v, err := c.genExpr(e)
		if err != nil {
			return err
		}
		vars = append(vars, v)
	}
	for _, e := range a.Rhs {
		typ, ok := c.inf.Types[e]
		if !ok {
			return fmt.Errorf("Couldn't determine type of variable: %s", e)
		}

		varTypes = append(varTypes, typ.Type)
	}

	if len(vars) == 1 {
		fmt.Fprint(gen.out, vars[0])
	} else {
		fmt.Fprintf(gen.out, "std::tie(%s)", strings.Join(vars, ", "))
	}

	var tupleOk bool
	switch a.Tok {
	case token.ADD_ASSIGN:
		fmt.Fprint(gen.out, " += ")
	case token.SUB_ASSIGN:
		fmt.Fprint(gen.out, " -= ")
	case token.MUL_ASSIGN:
		fmt.Fprint(gen.out, " *= ")<commit_msg>Generate type assertion<commit_after>// Copyright 2017 <NAME>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package compiler

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/token"
	"go/types"
	"io"
	"reflect"
	"strings"
)

type CppGen struct {
	fset *token.FileSet
	ast  *ast.File

	pkg *types.Package
	inf types.Info

	initPkgs []string

	input  io.Reader
	output io.Writer

	recvs VarStack

	idents int

	curVarType types.Type
	isTieAssign bool

	symbolFilter *SymbolFilter
}

type VarStack struct {
	vars  []*types.Var
	count int
}

func (s *VarStack) Push(v *types.Var) {
	s.vars = append(s.vars[:s.count], v)
	s.count++
}

func (s *VarStack) Pop() *types.Var {
	if s.count == 0 {
		return nil
	}
	s.count--
	return s.vars[s.count]
}

func (s *VarStack) Curr() *types.Var { return s.vars[s.count-1] }

func (s *VarStack) Lookup(name string) *types.Var {
	for cur := s.count - 1; cur >= 0; cur-- {
		if v := s.vars[cur]; v != nil && name == v.Name() {
			return v
		}
	}
	return nil
}

type basicTypeInfo struct {
	nilVal string
	typ    string
}

var basicTypeToCpp map[types.BasicKind]basicTypeInfo
var goTypeToBasic map[string]types.BasicKind

func init() {
	basicTypeToCpp = map[types.BasicKind]basicTypeInfo{
		types.Bool:          {"false", "bool"},
		types.UntypedBool:   {"false", "bool"},
		types.Int:           {"0", "int"},
		types.UntypedInt:    {"0", "int"},
		types.Int8:          {"0", "int8_t"},
		types.Int16:         {"0", "int16_t"},
		types.Int32:         {"0", "int32_t"},
		types.Int64:         {"0", "int64_t"},
		types.Uint:          {"0", "unsigned int"},
		types.Uint8:         {"0", "uint8_t"},
		types.Uint16:        {"0", "uint16_t"},
		types.Uint32:        {"0", "uint32_t"},
		types.Uint64:        {"0", "uint64_t"},
		types.Uintptr:       {"0", "uintptr_t"},
		types.Float32:       {"0", "float"},
		types.UntypedFloat:  {"0", "float"},
		types.Float64:       {"0", "double"},
		types.String:        {"\"\"", "std::string"},
		types.UntypedString: {"\"\"", "std::string"},
		types.UnsafePointer: {"std::nullptr", "void*"},
		types.Complex128:    {"0, 0", "moku::complex<double>"},
		types.Complex64:     {"0, 0", "moku::complex<float>"},
		types.UntypedRune:   {"0", "uint32_t"},
	}
	goTypeToBasic = map[string]types.BasicKind{
		"bool":       types.Bool,
		"int":        types.Int,
		"int8":       types.Int8,
		"int16":      types.Int16,
		"int32":      types.Int32,
		"int64":      types.Int64,
		"uint":       types.Uint,
		"uint8":      types.Uint8,
		"uint16":     types.Uint16,
		"uint32":     types.Uint32,
		"uint64":     types.Uint64,
		"uintptr":    types.Uintptr,
		"float32":    types.Float32,
		"float64":    types.Float64,
		"string":     types.String,
		"complex128": types.Complex128,
		"complex64":  types.Complex64,
	}
}

func (c *CppGen) newIdent() (ret string) {
	ret = fmt.Sprintf("_ident_%d_", c.idents)
	c.idents++
	return
}

func (c *CppGen) toTypeSig(t types.Type) (string, error) {
	f := func(t types.Type) (string, error) {
		switch typ := t.(type) {
		default:
			return "", fmt.Errorf("Unknown type: %s", reflect.TypeOf(typ))

		case *types.Chan:
			elemTyp, err := c.toTypeSig(typ.Elem())
			if err != nil {
				return "", nil
			}

			var dirMod string
			switch typ.Dir() {
			case types.SendRecv:
				dirMod = "true, true"
			case types.SendOnly:
				dirMod = "true, false"
			case types.RecvOnly:
				dirMod = "false, true"
			}

			return fmt.Sprintf("moku::channel<%s, %s>", elemTyp, dirMod), nil

		case *types.Map:
			k, err := c.toTypeSig(typ.Key())
			if err != nil {
				return "", err
			}

			v, err := c.toTypeSig(typ.Elem())
			if err != nil {
				return "", err
			}

			return fmt.Sprintf("std::map<%s, %s>", k, v), nil

		case *types.Slice:
			s, err := c.toTypeSig(typ.Elem())
			if err != nil {
				return "", err
			}

			return fmt.Sprintf("moku::slice<%s>", s), nil

		case *types.Array:
			s, err := c.toTypeSig(typ.Elem())
			if err != nil {
				return "", err
			}

			return fmt.Sprintf("std::vector<%s>", s), nil

		case *types.Pointer:
			s, err := c.toTypeSig(typ.Elem())
			if err != nil {
				return "", err
			}

			return fmt.Sprintf("%s*", s), nil

		case *types.Interface:
			if typ.Empty() {
				return "moku::empty_interface", nil
			}

			return c.toTypeSig(typ.Underlying())

		case *types.Named:
			switch typ.Obj().Name() {
			case "error":
				return "moku::error", nil
			default:
				return typ.Obj().Name(), nil
			}

		case *types.Basic:
			if v, ok := basicTypeToCpp[typ.Kind()]; ok {
				return v.typ, nil
			}

			return "", fmt.Errorf("Unsupported basic type: %s", typ)

		case *types.Tuple:
			var r []string

			items := typ.Len()
			for i := 0; i < items; i++ {
				s, err := c.toTypeSig(typ.At(i).Type())
				if err != nil {
					return "", err
				}

				r = append(r, s)
			}

			return strings.Join(r, ", "), nil

		case *types.Signature:
			var retType []string
			if r := typ.Results(); r != nil {
				s, err := c.toTypeSig(r)
				if err != nil {
					return "", err
				}
				retType = append(retType, s)
			} else {
				retType = append(retType, "void")
			}

			var paramTypes []string
			if p := typ.Params(); p != nil {
				s, err := c.toTypeSig(p)
				if err != nil {
					return "", err
				}
				paramTypes = append(paramTypes, s)
			}

			p := strings.Join(paramTypes, ", ")
			if len(retType) == 1 {
				r := retType[0]
				return fmt.Sprintf("std::function<%s(%s)>", r, p), nil
			}

			r := strings.Join(retType, ", ")
			return fmt.Sprintf("std::function<std::tuple<%s>(%s)>", r, p), nil
		}
	}

	sig, err := f(t)
	if err != nil {
		return sig, err
	}

	return sig, nil
}

func (c *CppGen) toNilVal(t types.Type) (string, error) {
	f := func(t types.Type) (string, error) {
		switch typ := t.(type) {
		case *types.Basic:
			if v, ok := basicTypeToCpp[typ.Kind()]; ok {
				return v.nilVal, nil
			}
		case *types.Pointer, *types.Signature:
			return "std::nullptr", nil

		case *types.Slice, *types.Map, *types.Chan,
			*types.Interface, *types.Named, *types.Array,
			*types.Struct:

			return "", nil
		}

		err := fmt.Errorf("Unknown nil value for type %s", reflect.TypeOf(t))
		return "", err
	}

	nilVal, err := f(t)
	if err != nil {
		return nilVal, err
	}

	if types.IsInterface(t) {
		return "", nil
	}

	return nilVal, err
}

func (c *CppGen) genFuncProto(name string, sig *types.Signature, out func(name, retType, params string) error) (err error) {
	sigParm := sig.Params()
	var params []string
	for p := 0; p < sigParm.Len(); p++ {
		parm := sigParm.At(p)
		typ, err := c.toTypeSig(parm.Type())
		if err != nil {
			return err
		}

		params = append(params, fmt.Sprintf("%s %s", typ, parm.Name()))
	}

	res := sig.Results()
	var retType string
	switch res.Len() {
	case 0:
		retType = "void"
	case 1:
		s, err := c.toTypeSig(res.At(0).Type())
		if err != nil {
			return err
		}
		retType = s
	default:
		var mult []string

		for r := 0; r < res.Len(); r++ {
			s, err := c.toTypeSig(res.At(r).Type())
			if err != nil {
				return err
			}

			mult = append(mult, s)
		}

		retType = fmt.Sprintf("std::tuple<%s>", strings.Join(mult, ", "))
	}

	return out(name, retType, strings.Join(params, ", "))
}

func (c *CppGen) genInterface(name string, iface *types.Interface) (err error) {
	fmt.Fprintf(c.output, "struct %s {\n", name)

	for m := iface.NumMethods(); m > 0; m-- {
		meth := iface.Method(m - 1)
		sig := meth.Type().(*types.Signature)

		err = c.genFuncProto(meth.Name(), sig, func(name, retType, params string) error {
			fmt.Fprintf(c.output, "virtual %s %s(%s) = 0;\n", retType, name, params)
			return nil
		})
		if err != nil {
			return err
		}
	}

	fmt.Fprintf(c.output, "};\n")
	return err
}

func (c *CppGen) genIfaceForType(n *types.Named, out func(ifaces []string) error) (err error) {
	// FIXME: this is highly inneficient and won't scale at all
	ifaces := make(map[string]struct{})
	ifaceMeths := make(map[string]struct{})
	for k, v := range c.inf.Types {
		if _, ok := k.(*ast.InterfaceType); !ok {
			continue
		}

		iface := v.Type.(*types.Interface)
		if !types.Implements(n, iface) {
			continue
		}

		for _, typ := range c.inf.Defs {
			if def, ok := typ.(*types.TypeName); ok {
				if !types.IsInterface(def.Type()) {
					continue
				}
				if !types.Implements(def.Type(), iface) {
					continue
				}

				for i := 0; i < iface.NumMethods(); i++ {
					ifaceMeths[iface.Method(i).Name()] = struct{}{}
				}

				derived := fmt.Sprintf("public %s", def.Name())
				ifaces[derived] = struct{}{}
				break
			}
		}
	}

	uniqIfaces := make([]string, 0, len(ifaces))
	for k := range ifaces {
		uniqIfaces = append(uniqIfaces, k)
	}
	if err = out(uniqIfaces); err != nil {
		return err
	}

	mset := types.NewMethodSet(n)
	for i := 0; i < mset.Len(); i++ {
		sel := mset.At(i)
		switch sel.Kind() {
		default:
			return fmt.Errorf("Kind %d not supported here", sel.Kind())

		case types.MethodVal:
			f := sel.Obj().(*types.Func)
			sig := f.Type().(*types.Signature)

			err = c.genFuncProto(f.Name(), sig, func(name, retType, params string) error {
				if _, virtual := ifaceMeths[f.Name()]; virtual {
					fmt.Fprintf(c.output, "virtual %s %s(%s) override;\n", retType, name, params)
				} else {
					fmt.Fprintf(c.output, "%s %s(%s);\n", retType, name, params)
				}
				return nil
			})
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func (c *CppGen) genStruct(name string, s *types.Struct, n *types.Named) (err error) {
	fmt.Fprintf(c.output, "struct %s", name)
	defer fmt.Fprintf(c.output, "};\n")

	return c.genIfaceForType(n, func(ifaces []string) error {
		if ifaces != nil {
			fmt.Fprintf(c.output, " : %s", strings.Join(ifaces, ", "))
		}

		fmt.Fprint(c.output, " {\n")
		numFields := s.NumFields()
		var nilCmp []string
		for f := 0; f < numFields; f++ {
			f := s.Field(f)

			typ, err := c.toTypeSig(f.Type())
			if err != nil {
				return fmt.Errorf("Couldn't generate field: %s", err)
			}

			nilVal, err := c.toNilVal(f.Type())
			if err != nil {
				return fmt.Errorf("Couldn't determine nil value for %s: %s", name, err)
			}

			if nilVal != "" {
				nilCmp = append(nilCmp, fmt.Sprintf("%s == %s", f.Name(), nilVal))

				fmt.Fprintf(c.output, "%s %s{%s};\n", typ, f.Name(), nilVal)
				continue
			}

			nilCmp = append(nilCmp, fmt.Sprintf("%s._isNil_()", f.Name()))
			fmt.Fprintf(c.output, "%s %s;\n", typ, f.Name())
		}

		fmt.Fprintf(c.output, "bool _isNil_() const { return %s; }", strings.Join(nilCmp, " && "))
		return nil
	})
}

func (c *CppGen) genBasicType(name string, b *types.Basic, n *types.Named) (err error) {
	fmt.Fprintf(c.output, "struct %s", name)
	defer fmt.Fprintf(c.output, "};\n")

	return c.genIfaceForType(n, func(ifaces []string) error {
		typ, err := c.toTypeSig(b.Underlying())
		if err != nil {
			return fmt.Errorf("Could not determine underlying type: %s", err)
		}

		nilValue, err := c.toNilVal(b.Underlying())
		if err != nil {
			return fmt.Errorf("Could not determine nil value for type %s: %s", typ, err)
		}

		base := []string{fmt.Sprintf("moku::basic<%s>", typ)}
		for _, iface := range ifaces {
			base = append(base, iface)
		}

		fmt.Fprintf(c.output, ": %s {\n", strings.Join(base, ", "))
		fmt.Fprintf(c.output, "%s() : moku::basic<%s>{%s} {}\n", name, typ, nilValue)
		fmt.Fprintf(c.output, "bool _isNil_() const { return %s(this) == %s; }", typ, nilValue)

		return nil
	})
}

func (c *CppGen) genNamedType(name string, n *types.Named) (err error) {
	switch t := n.Underlying().(type) {
	default:
		return fmt.Errorf("What to do with the named type %v?", reflect.TypeOf(t))

	case *types.Interface:
		return c.genInterface(name, t)

	case *types.Struct:
		return c.genStruct(name, t, n)

	case *types.Basic:
		return c.genBasicType(name, t, n)
	}
}

func (c *CppGen) genPrototype(name string, sig *types.Signature) error {
	return c.genFuncProto(name, sig, func(name, retType, params string) error {
		fmt.Fprintf(c.output, "%s %s(%s);\n", retType, name, params)
		return nil
	})
}

func (c *CppGen) genVar(gen *nodeGen, v *types.Var, mainBlock bool) error {
	typ, err := c.toTypeSig(v.Type())
	if err != nil {
		return fmt.Errorf("Couldn't get type signature for variable: %s", err)
	}

	nilVal, err := c.toNilVal(v.Type())
	if err != nil {
		return fmt.Errorf("Couldn't get nil value for variable: %s", err)
	}

	switch {
	case mainBlock:
		if !v.Exported() {
			fmt.Fprint(gen.out, "static ")
		}
		fmt.Fprintf(gen.out, "%s %s;\n", typ, v.Name())
	case gen.escapees[v]:
		println(v, "escapes")
		fmt.Fprintf(gen.out, "%s %s{%s}; /* escapes */\n", typ, v.Name(), nilVal)
	default:
		fmt.Fprintf(gen.out, "%s %s{%s};\n", typ, v.Name(), nilVal)
	}

	return nil
}

func (c *CppGen) genConst(gen *nodeGen, k *types.Const, mainBlock bool) error {
	typ, err := c.toTypeSig(k.Type())
	if err != nil {
		return fmt.Errorf("Couldn't get type signature for variable: %s", err)
	}

	if mainBlock {
		if !k.Exported() {
			fmt.Fprint(gen.out, "static ")
		}
		fmt.Fprintf(gen.out, "constexpr %s %s{%s};\n", typ, k.Name(), k.Val())
	} else {
		fmt.Fprintf(gen.out, "constexpr %s %s{%s};\n", typ, k.Name(), k.Val())
	}

	return nil
}

func (c *CppGen) genNamespace(p *types.Package) (err error) {
	s := p.Scope()

	if c.symbolFilter.Once(s, "#pragma once") {
		fmt.Fprintln(c.output, "#pragma once")
	}

	for _, imp := range p.Imports() {
		include := fmt.Sprintf("#include \"%s.h\"", imp.Name())
		if c.symbolFilter.Once(s, include) {
			fmt.Fprintln(c.output, include)
		}
	}

	if len(s.Names()) == 0 {
		return nil
	}

	if c.symbolFilter.Once(s, "namespace "+p.Name()) {
		fmt.Fprintf(c.output, "namespace %s {\n", p.Name())
		defer fmt.Fprintf(c.output, "} // namespace %s\n", p.Name())
	}

	genTypeProto := func(name string, obj types.Object) error {
		switch t := obj.Type().(type) {
		default:
			return nil

		case *types.Named:
			return c.genNamedType(name, t)

		case *types.Signature:
			return c.genPrototype(name, t)
		}
	}

	for _, name := range s.Names() {
		obj := s.Lookup(name)

		if !c.symbolFilter.Once(s, name) {
			continue
		}

		if name == "main" {
			name = "_main"
		}

		switch t := obj.(type) {
		case *types.Func:
			if t.Name() == "init" {
				c.initPkgs = append(c.initPkgs, p.Name())
			}
			if err = genTypeProto(name, obj); err != nil {
				return err
			}
		case *types.TypeName:
			if err = genTypeProto(name, obj); err != nil {
				return err
			}
		case *types.Var:
			gen := nodeGen{out: c.output}
			if err = c.genVar(&gen, t, true); err != nil {
				return err
			}
		case *types.Const:
			gen := nodeGen{out: c.output}
			if err = c.genConst(&gen, t, true); err != nil {
				return err
			}
		default:
			return fmt.Errorf("Don't know how to generate: %s", reflect.TypeOf(t))
		}
	}

	return nil
}

func (c *CppGen) genMapType(m *ast.MapType) (string, error) {
	k, err := c.genExpr(m.Key)
	if err != nil {
		return "", err
	}
	v, err := c.genExpr(m.Value)
	if err != nil {
		return "", err
	}

	return fmt.Sprintf("std::map<%s, %s>", k, v), nil
}

func (c *CppGen) genCallExpr(ce *ast.CallExpr) (string, error) {
	fun, err := c.genExpr(ce.Fun)
	if err != nil {
		return "", err
	}

	var args []string
	for _, arg := range ce.Args {
		argExp, err := c.genExpr(arg)
		if err != nil {
			return "", err
		}

		args = append(args, argExp)
	}
	if ce.Ellipsis.IsValid() {
		// TODO
	}

	return fmt.Sprintf("%s(%s)", fun, strings.Join(args, ", ")), nil
}

func (c *CppGen) genBasicLit(b *ast.BasicLit) (string, error) {
	switch b.Kind {
	default:
		return "", fmt.Errorf("Unknown basic literal type: %+v", b)

	case token.INT, token.FLOAT, token.CHAR, token.STRING:
		return b.Value, nil

	case token.IMAG:
		return "", fmt.Errorf("Imaginary numbers not supported")
	}
}

func (c *CppGen) genIdent(i *ast.Ident) (string, error) {
	if this := c.recvs.Lookup(i.Name); this != nil {
		return "this", nil
	}
	if basicTyp, ok := goTypeToBasic[i.Name]; ok {
		return basicTypeToCpp[basicTyp].typ, nil
	}
	return i.Name, nil
}

func (c *CppGen) genStarExpr(s *ast.StarExpr) (string, error) {
	return c.genUnaryExpr(&ast.UnaryExpr{X: s.X, Op: token.MUL})
}

func (c *CppGen) genKeyValueExpr(kv *ast.KeyValueExpr) (string, error) {
	key, err := c.genExpr(kv.Key)
	if err != nil {
		return "", err
	}
	val, err := c.genExpr(kv.Value)
	if err != nil {
		return "", err
	}

	switch c.curVarType.(type) {
	default:
		return fmt.Sprintf("{%s, %s}", key, val), nil

	case *types.Named:
		return fmt.Sprintf("%s: %s", key, val), nil
	}
}

func (c *CppGen) genTypeAssertExpr(ta *ast.TypeAssertExpr) (string, error) {
	expr, err := c.genExpr(ta.X)
	if err != nil {
		return "", err
	}

	typ, err := c.genExpr(ta.Type)
	if err != nil {
		return "", err
	}

	if c.isTieAssign {
		return fmt.Sprintf("moku::try_type_assert<%s>(%s)", typ, expr), nil
	}

	return fmt.Sprintf("moku::type_assert<%s>(%s)", typ, expr), nil
}

func (c *CppGen) genExpr(x ast.Expr) (string, error) {
	switch x := x.(type) {
	default:
		return "", fmt.Errorf("Couldn't generate expression with type: %s", reflect.TypeOf(x))

	case *ast.TypeAssertExpr:
		return c.genTypeAssertExpr(x)

	case *ast.KeyValueExpr:
		return c.genKeyValueExpr(x)

	case *ast.StarExpr:
		return c.genStarExpr(x)

	case *ast.FuncLit:
		return c.genFuncLit(x)

	case *ast.CompositeLit:
		return c.genCompositeLit(x)

	case *ast.BinaryExpr:
		return c.genBinaryExpr(x)

	case *ast.CallExpr:
		return c.genCallExpr(x)

	case *ast.SelectorExpr:
		return c.genSelectorExpr(x)

	case *ast.ParenExpr:
		return c.genParenExpr(x)

	case *ast.SliceExpr:
		return c.genSliceExpr(x)

	case *ast.IndexExpr:
		return c.genIndexExpr(x)

	case *ast.UnaryExpr:
		return c.genUnaryExpr(x)

	case *ast.ArrayType:
		return c.genArrayType(x)

	case *ast.MapType:
		return c.genMapType(x)

	case *ast.BasicLit:
		return c.genBasicLit(x)

	case *ast.Ident:
		return c.genIdent(x)
	}
}

func (c *CppGen) genInit() bool {
	for ident, _ := range c.inf.Defs {
		if ident.Name == "init" {
			fmt.Fprintf(c.output, "void init();\n")
			return true
		}
	}

	return false
}

func (c *CppGen) genMain() (err error) {
	hasInit := c.genInit()

	fmt.Fprintf(c.output, "int main() {\n")

	for _, pkg := range c.initPkgs {
		fmt.Fprintf(c.output, "%s::init();\n", pkg)
	}

	if hasInit {
		fmt.Fprintf(c.output, "init();\n")
	}

	for _, init := range c.inf.InitOrder {
		if len(init.Lhs) == 1 {
			fmt.Fprintf(c.output, "%s", init.Lhs[0].Name())
		} else {
			var tie []string

			for _, lhs := range init.Lhs {
				tie = append(tie, lhs.Name())
			}

			fmt.Fprintf(c.output, "std::tie(%s)", strings.Join(tie, ", "))
		}

		expr, err := c.genExpr(init.Rhs)
		if err != nil {
			return fmt.Errorf("Couldn't write initialization code: %s", err)
		}

		fmt.Fprintf(c.output, "= %s;\n", expr)
	}

	fmt.Fprintf(c.output, "_main();\n")
	fmt.Fprintf(c.output, "return 0;\n")
	fmt.Fprintf(c.output, "}\n")

	return nil
}

type nodeGen struct {
	out      io.Writer
	hasDefer bool

	// SwitchStmt generation
	labels         []string
	curLbl, defLbl int

	escapees map[*types.Var]bool
}

func (c *CppGen) genComment(gen *nodeGen, comment *ast.Comment) error {
	fmt.Fprintf(gen.out, "/* %s */", comment.Text)
	return nil
}

func (c *CppGen) genFuncDecl(gen *nodeGen, f *ast.FuncDecl) (err error) {
	var typ types.Object
	typ, ok := c.inf.Defs[f.Name]
	if !ok {
		return fmt.Errorf("Could not find type for func %s", f.Name.Name)
	}

	name := f.Name.Name
	if name == "main" {
		name = "_main"
	}

	fun := typ.(*types.Func)
	sig := fun.Type().(*types.Signature)
	recv := sig.Recv()
	err = c.genFuncProto(name, sig, func(name, retType, params string) (err error) {
		if recv != nil {
			var typ string
			switch t := recv.Type().(type) {
			case *types.Named:
				typ = t.Obj().Name()
			case *types.Pointer:
				if typ, err = c.toTypeSig(t.Elem()); err != nil {
					return err
				}
			}
			name = fmt.Sprintf("%s::%s", typ, name)
		}
		fmt.Fprintf(gen.out, "%s %s(%s)\n", retType, name, params)
		return nil
	})
	if err != nil {
		return err
	}

	c.recvs.Push(recv)
	defer c.recvs.Pop()

	filt := func(name string) bool {
		if recv != nil && recv.Name() == name {
			return false
		}

		parms := sig.Params()
		for p := 0; p < parms.Len(); p++ {
			if parms.At(p).Name() == name {
				return false
			}
		}

		return true
	}
	if err = c.genScopeAndBody(gen, f.Body, f.Type, true, filt); err != nil {
		return err
	}

	return err
}

func (c *CppGen) genAssignStmt(gen *nodeGen, a *ast.AssignStmt) (err error) {
	var varTypes []types.Type
	var vars []string

	defer func() { c.curVarType = nil }()

	for _, e := range a.Lhs {
		v, err := c.genExpr(e)
		if err != nil {
			return err
		}
		vars = append(vars, v)
	}
	for _, e := range a.Rhs {
		typ, ok := c.inf.Types[e]
		if !ok {
			return fmt.Errorf("Couldn't determine type of variable: %s", e)
		}

		varTypes = append(varTypes, typ.Type)
	}

	if len(vars) == 1 {
		fmt.Fprint(gen.out, vars[0])
		c.isTieAssign = false
	} else {
		fmt.Fprintf(gen.out, "std::tie(%s)", strings.Join(vars, ", "))
		c.isTieAssign = true
	}

	var tupleOk bool
	switch a.Tok {
	case token.ADD_ASSIGN:
		fmt.Fprint(gen.out, " += ")
	case token.SUB_ASSIGN:
		fmt.Fprint(gen.out, " -= ")
	case token.MUL_ASSIGN:
		fmt.Fprint(gen.out, " *= ")
{'fraction_non_alphanumeric': 0.1850217111227752, 'fraction_numerical': 0.006441761702533759, 'mean_word_length': 3.065470417070805, 'pattern_counts': {'":': 38, '<': 47, '<?xml version=': 0, '>': 37, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6111506', 'n_tokens_mistral': 18346, 'n_tokens_neox': 16427, 'n_words': 4045}