// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Binary package import.
// See bexport.go for the export data format and how
// to make a format change.

package gc

import (
	"bufio"
	"encoding/binary"
	"fmt"
	"math/big"
	"strconv"
	"strings"
)

// The overall structure of Import is symmetric to Export: For each
// export method in bexport.go there is a matching and symmetric method
// in bimport.go. Changing the export format requires making symmetric
// changes to bimport.go and bexport.go.

type importer struct {
	in      *bufio.Reader
	buf     []byte // reused for reading strings
	version int    // export format version

	// object lists, in order of deserialization
	strList       []string
	pkgList       []*Pkg
	typList       []*Type
	funcList      []*Node // nil entry means already declared
	trackAllTypes bool

	// for delayed type verification
	cmpList []struct{ pt, t *Type }

	// position encoding
	posInfoFormat bool
	prevFile      string
	prevLine      int

	// debugging support
	debugFormat bool
	read        int // bytes read
}

// Import populates importpkg from the serialized package data.
func Import(in *bufio.Reader) {
	p := importer{
		in:      in,
		version: -1,           // unknown version
		strList: []string{""}, // empty string is mapped to 0
	}

	// read version info
	var versionstr string
	if b := p.rawByte(); b == 'c' || b == 'd' {
		// Go1.7 encoding; first byte encodes low-level
		// encoding format (compact vs debug).
		// For backward-compatibility only (avoid problems with
		// old installed packages). Newly compiled packages use
		// the extensible format string.
		// TODO(gri) Remove this support eventually; after Go1.8.
		if b == 'd' {
			p.debugFormat = true
		}
		p.trackAllTypes = p.rawByte() == 'a'
		p.posInfoFormat = p.bool()
		versionstr = p.string()
		if versionstr == "v1" {
			p.version = 0
		}
	} else {
		// Go1.8 extensible encoding
		// read version string and extract version number (ignore anything after the version number)
		versionstr = p.rawStringln(b)
		if s := strings.SplitN(versionstr, " ", 3); len(s) >= 2 && s[0] == "version" {
			if v, err := strconv.Atoi(s[1]); err == nil && v > 0 {
				p.version = v
			}
		}
	}

	// read version specific flags - extend as necessary
	switch p.version {
	// case 4:
	// 	...
	//	fallthrough
	case 3, 2, 1:
		p.debugFormat = p.rawStringln(p.rawByte()) == "debug"
		p.trackAllTypes = p.bool()
		p.posInfoFormat = p.bool()
	case 0:
		// Go1.7 encoding format - nothing to do here
	default:
		formatErrorf("unknown export format version %d (%q)", p.version, versionstr)
	}

	// --- generic export data ---

	// populate typList with predeclared "known" types
	p.typList = append(p.typList, predeclared()...)

	// read package data
	p.pkg()

	// defer some type-checking until all types are read in completely
	tcok := typecheckok
	typecheckok = true
	defercheckwidth()

	// read objects

	// phase 1
	objcount := 0
	for {
		tag := p.tagOrIndex()
		if tag == endTag {
			break
		}
		p.obj(tag)
		objcount++
	}

	// self-verification
	if count := p.int(); count != objcount {
		formatErrorf("got %d objects; want %d", objcount, count)
	}

	// --- compiler-specific export data ---

	// read compiler-specific flags

	// phase 2
	objcount = 0
	for {
		tag := p.tagOrIndex()
		if tag == endTag {
			break
		}
		p.obj(tag)
		objcount++
	}

	// self-verification
	if count := p.int(); count != objcount {
		formatErrorf("got %d objects; want %d", objcount, count)
	}

	// read inlineable functions bodies
	if dclcontext != PEXTERN {
		formatErrorf("unexpected context %d", dclcontext)
	}

	objcount = 0
	for i0 := -1; ; {
		i := p.int() // index of function with inlineable body
		if i < 0 {
			break
		}

		// don't process the same function twice
		if i <= i0 {
			formatErrorf("index not increasing: %d <= %d", i, i0)
		}
		i0 = i

		if funcdepth != 0 {
			formatErrorf("unexpected Funcdepth %d", funcdepth)
		}

		// Note: In the original code, funchdr and funcbody are called for
		// all functions (that were not yet imported). Now, we are calling
		// them only for functions with inlineable bodies. funchdr does
		// parameter renaming which doesn't matter if we don't have a body.

		if f := p.funcList[i]; f != nil {
			// function not yet imported - read body and set it
			funchdr(f)
			body := p.stmtList()
			if body == nil {
				// Make sure empty body is not interpreted as
				// no inlineable body (see also parser.fnbody)
				// (not doing so can cause significant performance
				// degradation due to unnecessary calls to empty
				// functions).
				body = []*Node{nod(OEMPTY, nil, nil)}
			}
			f.Func.Inl.Set(body)
			funcbody(f)
		} else {
			// function already imported - read body but discard declarations
			dclcontext = PDISCARD // throw away any declarations
			p.stmtList()
			dclcontext = PEXTERN
		}

		objcount++
	}

	// self-verification
	if count := p.int(); count != objcount {
		formatErrorf("got %d functions; want %d", objcount, count)
	}

	if dclcontext != PEXTERN {
		formatErrorf("unexpected context %d", dclcontext)
	}

	p.verifyTypes()

	// --- end of export data ---

	typecheckok = tcok
	resumecheckwidth()

	testdclstack() // debugging only
}

func formatErrorf(format string, args ...interface{}) {
	if debugFormat {
		Fatalf(format, args...)
	}

	yyerror("cannot import %q due to version skew - reinstall package (%s)",
		importpkg.Path, fmt.Sprintf(format, args...))
	errorexit()
}

func (p *importer) verifyTypes() {
	for _, pair := range p.cmpList {
		pt := pair.pt
		t := pair.t
		if !eqtype(pt.Orig, t) {
			formatErrorf("inconsistent definition for type %v during import\n\t%L (in %q)\n\t%L (in %q)", pt.Sym, pt, pt.Sym.Importdef.Path, t, importpkg.Path)
		}
	}
}

// numImport tracks how often a package with a given name is imported.
// It is used to provide a better error message (by using the package
// path to disambiguate) if a package that appears multiple times with
// the same name appears in an error message.
var numImport = make(map[string]int)

func (p *importer) pkg() *Pkg {
	// if the package was seen before, i is its index (>= 0)
	i := p.tagOrIndex()
	if i >= 0 {
		return p.pkgList[i]
	}

	// otherwise, i is the package tag (< 0)
	if i != packageTag {
		formatErrorf("expected package tag, found tag = %d", i)
	}

	// read package data
	name := p.string()
	path := p.string()

	// we should never see an empty package name
	if name == "" {
		formatErrorf("empty package name for path %q", path)
	}

	// we should never see a bad import path
	if isbadimport(path) {
		formatErrorf("bad package path %q for package %s", path, name)
	}

	// an empty path denotes the package we are currently importing;
	// it must be the first package we see
	if (path == "") != (len(p.pkgList) == 0) {
		formatErrorf("package path %q for pkg index %d", path, len(p.pkgList))
	}

	// add package to pkgList
	pkg := importpkg
	if path != "" {
		pkg = mkpkg(path)
	}
	if pkg.Name == "" {
		pkg.Name = name
		numImport[name]++
	} else if pkg.Name != name {
		yyerror("conflicting package names %s and %s for path %q", pkg.Name, name, path)
	}
	if myimportpath != "" && path == myimportpath {
		yyerror("import %q: package depends on %q (import cycle)", importpkg.Path, path)
		errorexit()
	}
	p.pkgList = append(p.pkgList, pkg)

	return pkg
}

func idealType(typ *Type) *Type {
	if typ.IsUntyped() {
		// canonicalize ideal types
		typ = Types[TIDEAL]
	}
	return typ
}

func (p *importer) obj(tag int) {
	switch tag {
	case constTag:
		p.pos()
		sym := p.qualifiedName()
		typ := p.typ()
		val := p.value(typ)
		importconst(sym, idealType(typ), nodlit(val))

	case typeTag:
		p.typ()

	case varTag:
		p.pos()
		sym := p.qualifiedName()
		typ := p.typ()
		importvar(sym, typ)

	case funcTag:
		p.pos()
		sym := p.qualifiedName()
		params := p.paramList()
		result := p.paramList()

		sig := functypefield(nil, params, result)
		importsym(sym, ONAME)
		if sym.Def != nil && sym.Def.Op == ONAME {
			// function was imported before (via another import)
			if !eqtype(sig, sym.Def.Type) {
				formatErrorf("inconsistent definition for func %v during import\n\t%v\n\t%v", sym, sym.Def.Type, sig)
			}
			p.funcList = append(p.funcList, nil)
			break
		}

		n := newfuncname(sym)
		n.Type = sig
		declare(n, PFUNC)
		p.funcList = append(p.funcList, n)
		importlist = append(importlist, n)

		if Debug['E'] > 0 {
			fmt.Printf("import [%q] func %v \n", importpkg.Path, n)
			if Debug['m'] > 2 && n.Func.Inl.Len() != 0 {
				fmt.Printf("inl body: %v\n", n.Func.Inl)
			}
		}

	case aliasTag:
		p.pos()
		alias := importpkg.Lookup(p.string())
		orig := p.qualifiedName()

		// Although the protocol allows the alias to precede the original,
		// this never happens in files produced by gc.
		alias.Flags |= SymAlias
		alias.Def = orig.Def
		importsym(alias, orig.Def.Op)

	default:
		formatErrorf("unexpected object (tag = %d)", tag)
	}
}

func (p *importer) pos() {
	if !p.posInfoFormat {
		return
	}

	file := p.prevFile
	line := p.prevLine
	if delta := p.int(); delta != 0 {
		// line changed
		line += delta
	} else if n := p.int(); n >= 0 {
		// file changed
		file = p.prevFile[:n] + p.string()
		p.prevFile = file
		line = p.int()
	}
	p.prevLine = line

	// TODO(gri) register new position
}

func (p *importer) newtyp(etype EType) *Type {
	t := typ(etype)
	if p.trackAllTypes {
		p.typList = append(p.typList, t)
	}
	return t
}

// importtype declares that pt, an imported named type, has underlying type t.
func (p *importer) importtype(pt, t *Type) {
	if pt.Etype == TFORW {
		n := pt.nod
		copytype(pt.nod, t)
		pt.nod = n // unzero nod
		pt.Sym.Importdef = importpkg
		pt.Sym.Lastlineno = lineno
		declare(n, PEXTERN)
		checkwidth(pt)
	} else {
		// pt.Orig and t must be identical.
		if p.trackAllTypes {
			// If we track all types, t may not be fully set up yet.
			// Collect the types and verify identity later.
			p.cmpList = append(p.cmpList, struct{ pt, t *Type }{pt, t})
		} else if !eqtype(pt.Orig, t) {
			yyerror("inconsistent definition for type %v during import\n\t%L (in %q)\n\t%L (in %q)", pt.Sym, pt, pt.Sym.Importdef.Path, t, importpkg.Path)
		}
	}

	if Debug['E'] != 0 {
		fmt.Printf("import type %v %L\n", pt, t)
	}
}

func (p *importer) typ() *Type {
	// if the type was seen before, i is its index (>= 0)
	i := p.tagOrIndex()
	if i >= 0 {
		return p.typList[i]
	}

	// otherwise, i is the type tag (< 0)
	var t *Type
	switch i {
	case namedTag:
		p.pos()
		tsym := p.qualifiedName()

		t = pkgtype(tsym)
		p.typList = append(p.typList, t)

		// read underlying type
		t0 := p.typ()
		p.importtype(t, t0)

		// interfaces don't have associated methods
		if t0.IsInterface() {
			break
		}

		// set correct import context (since p.typ() may be called
		// while importing the body of an inlined function)
		savedContext := dclcontext
		dclcontext = PEXTERN

		// read associated methods
		for i := p.int(); i > 0; i-- {
			p.pos()
			sym := p.fieldSym()

			// during import unexported method names should be in the type's package
			if !exportname(sym.Name) && sym.Pkg != tsym.Pkg {
				Fatalf("imported method name %+v in wrong package %s\n", sym, tsym.Pkg.Name)
			}

			recv := p.paramList() // TODO(gri) do we need a full param list for the receiver?
			params := p.paramList()
			result := p.paramList()
			nointerface := p.bool()

			base := recv[0].Type
			star := false
			if base.IsPtr() {
				base = base.Elem()
				star = true
			}

			n := methodname0(sym, star, base.Sym)
			n.Type = functypefield(recv[0], params, result)
			checkwidth(n.Type)
			addmethod(sym, n.Type, false, nointerface)
			p.funcList = append(p.funcList, n)
			importlist = append(importlist, n)

			// (comment from parser.go)
			// inl.C's inlnode in on a dotmeth node expects to find the inlineable body as
			// (dotmeth's type).Nname.Inl, and dotmeth's type has been pulled
			// out by typecheck's lookdot as this $$.ttype. So by providing
			// this back link here we avoid special casing there.
			n.Type.SetNname(n)

			if Debug['E'] > 0 {
				fmt.Printf("import [%q] meth %v \n", importpkg.Path, n)
				if Debug['m'] > 2 && n.Func.Inl.Len() != 0 {
					fmt.Printf("inl body: %v\n", n.Func.Inl)
				}
			}
		}

		dclcontext = savedContext

	case arrayTag:
		t = p.newtyp(TARRAY)
		bound := p.int64()
		elem := p.typ()
		t.Extra = &ArrayType{Elem: elem, Bound: bound}

	case sliceTag:
		t = p.newtyp(TSLICE)
		elem := p.typ()
		t.Extra = SliceType{Elem: elem}

	case dddTag:
		t = p.newtyp(TDDDFIELD)
		t.Extra = DDDFieldType{T: p.typ()}

	case structTag:
		t = p.newtyp(TSTRUCT)
		t.SetFields(p.fieldList())
		checkwidth(t)

	case pointerTag:
		t = p.newtyp(Tptr)
		t.Extra = PtrType{Elem: p.typ()}

	case signatureTag:
		t = p.newtyp(TFUNC)
		params := p.paramList()
		result := p.paramList()
		functypefield0(t, nil, params, result)

	case interfaceTag:
		t = p.newtyp(TINTER)
		if p.int() != 0 {
			formatErrorf("unexpected embedded interface")
		}
		t.SetFields(p.methodList())
		checkwidth(t)

	case mapTag:
		t = p.newtyp(TMAP)
		mt := t.MapType()
		mt.Key = p.typ()
		mt.Val = p.typ()

	case chanTag:
		t = p.newtyp(TCHAN)
		ct := t.ChanType()
		ct.Dir = ChanDir(p.int())
		ct.Elem = p.typ()

	default:
		formatErrorf("unexpected type (tag = %d)", i)
	}

	if t == nil {
		formatErrorf("nil type (type tag = %d)", i)
	}

	return t
}

func (p *importer) qualifiedName() *Sym {
	name := p.string()
	pkg := p.pkg()
	return pkg.Lookup(name)
}

func (p *importer) fieldList() (fields []*Field) {
	if n := p.int(); n > 0 {
		fields = make([]*Field, n)
		for i := range fields {
			fields[i] = p.field()
		}
	}
	return
}

func (p *importer) field() *Field {
	p.pos()
	sym := p.fieldName()
	typ := p.typ()
	note := p.string()

	f := newField()
	if sym.Name == "" {
		// anonymous field - typ must be T or *T and T must be a type name
		s := typ.Sym
		if s == nil && typ.IsPtr() {
			s = typ.Elem().Sym // deref
		}
		sym = sym.Pkg.Lookup(s.Name)
		f.Embedded = 1
	}

	f.Sym = sym
	f.Nname = newname(sym)
	f.Type = typ
	f.Note = note

	return f
}

func (p *importer) methodList() (methods []*Field) {
	if n := p.int(); n > 0 {
		methods = make([]*Field, n)
		for i := range methods {
			methods[i] = p.method()
		}
	}
	return
}

func (p *importer) method() *Field {
	p.pos()
	sym := p.fieldName()
	params := p.paramList()
	result := p.paramList()

	f := newField()
	f.Sym = sym
	f.Nname = newname(sym)
	f.Type = functypefield(fakethisfield(), params, result)
	return f
}

func (p *importer) fieldName() *Sym {
	name := p.string()
	if p.version == 0 && name == "_" {
		// version 0 didn't export a package for _ fields
		// but used the builtin package instead
		return builtinpkg.Lookup(name)
	}
	pkg := localpkg
	if name != "" && !exportname(name) {
		if name == "?" {
			name = ""
		}
		pkg = p.pkg()
	}
	return pkg.Lookup(name)
}

func (p *importer) paramList() []*Field {
	i := p.int()
	if i == 0 {
		return nil
	}
	// negative length indicates unnamed parameters
	named := true
	if i < 0 {
		i = -i
		named = false
	}
	// i > 0
	fs := make([]*Field, i)
	for i := range fs {
		fs[i] = p.param(named)
	}
	return fs
}

func (p *importer) param(named bool) *Field {
	f := newField()
	f.Type = p.typ()
	if f.Type.Etype == TDDDFIELD {
		// TDDDFIELD indicates wrapped ... slice type
		f.Type = typSlice(f.Type.DDDField())
		f.Isddd = true
	}

	if named {
		name := p.string()
		if name == "" {
			formatErrorf("expected named parameter")
		}
		// TODO(gri) Supply function/method package rather than
		// encoding the package for each parameter repeatedly.
		pkg := localpkg
		if name != "_" {
			pkg = p.pkg()
		}
		f.Sym = pkg.Lookup(name)
		f.Nname = newname(f.Sym)
	}

	// TODO(gri) This is compiler-specific (escape info).
	// Move into compiler-specific section eventually?
	f.Note = p.string()

	return f
}

func (p *importer) value(typ *Type) (x Val) {
	switch tag := p.tagOrIndex(); tag {
	case falseTag:
		x.U = false

	case trueTag:
		x.U = true

	case int64Tag:
		u := new(Mpint)
		u.SetInt64(p.int64())
		u.Rune = typ == idealrune
		x.U = u

	case floatTag:
		f := newMpflt()
		p.float(f)
		if typ == idealint || typ.IsInteger() {
			// uncommon case: large int encoded as float
			u := new(Mpint)
			u.SetFloat(f)
			x.U = u
			break
		}
		x.U = f

	case complexTag:
		u := new(Mpcplx)
		p.float(&u.Real)
		p.float(&u.Imag)
		x.U = u

	case stringTag:
		x.U = p.string()

	case unknownTag:
		formatErrorf("unknown constant (importing package with errors)")

	case nilTag:
		x.U = new(NilVal)

	default:
		formatErrorf("unexpected value tag %d", tag)
	}

	// verify ideal type
	if typ.IsUntyped() && untype(x.Ctype()) != typ {
		formatErrorf("value %v and type %v don't match", x, typ)
	}

	return
}

func (p *importer) float(x *Mpflt) {
	sign := p.int()
	if sign == 0 {
		x.SetFloat64(0)
		return
	}

	exp := p.int()
	mant := new(big.Int).SetBytes([]byte(p.string()))

	m := x.Val.SetInt(mant)
	m.SetMantExp(m, exp-mant.BitLen())
	if sign < 0 {
		m.Neg(m)
	}
}

// ----------------------------------------------------------------------------
// Inlined function bodies

// Approach: Read nodes and use them to create/declare the same data structures
// as done originally by the (hidden) parser by closely following the parser's
// original code. In other words, "parsing" the import data (which happens to
// be encoded in binary rather textual form) is the best way at the moment to
// re-establish the syntax tree's invariants. At some future point we might be
// able to avoid this round-about way and create the rewritten nodes directly,
// possibly avoiding a lot of duplicate work (name resolution, type checking).
//
// Refined nodes (e.g., ODOTPTR as a refinement of OXDOT) are exported as their
// unrefined nodes (since this is what the importer uses). The respective case
// entries are unreachable in the importer.

func (p *importer) stmtList() []*Node {
	var list []*Node
	for {
		n := p.node()
		if n == nil {
			break
		}
		// OBLOCK nodes may be created when importing ODCL nodes - unpack them
		if n.Op == OBLOCK {
			list = append(list, n.List.Slice()...)
		} else {
			list = append(list, n)
		}
	}
	return list
}

func (p *importer) exprList() []*Node {
	var list []*Node
	for {
		n := p.expr()
		if n == nil {
			break
		}
		list = append(list, n)
	}
	return list
}

func (p *importer) elemList() []*Node {
	c := p.int()
	list := make([]*Node, c)
	for i := range list {
		s := p.fieldSym()
		list[i] = nodSym(OSTRUCTKEY, p.expr(), s)
	}
	return list
}

func (p *importer) expr() *Node {
	n := p.node()
	if n != nil && n.Op == OBLOCK {
		Fatalf("unexpected block node: %v", n)
	}
	return n
}

// TODO(gri) split into expr and stmt
func (p *importer) node() *Node {
	switch op := p.op(); op {
	// expressions
	// case OPAREN:
	// 	unreachable - unpacked by exporter

	// case ODDDARG:
	//	unimplemented

	case OLITERAL:
		typ := p.typ()
		n := nodlit(p.value(typ))
		if !typ.IsUntyped() {
			// Type-checking simplifies unsafe.Pointer(uintptr(c))
			// to unsafe.Pointer(c) which then cannot type-checked
			// again. Re-introduce explicit uintptr(c) conversion.
			// (issue 16317).
			if typ.IsUnsafePtr() {
				conv := nod(OCALL, typenod(Types[TUINTPTR]), nil)
				conv.List.Set1(n)
				n = conv
			}
			conv := nod(OCALL, typenod(typ), nil)
			conv.List.Set1(n)
			n = conv
		}
		return n

	case ONAME:
		return mkname(p.sym())

	// case OPACK, ONONAME:
	// 	unreachable - should have been resolved by typechecking

	case OTYPE:
		if p.bool() {
			return mkname(p.sym())
		}
		return typenod(p.typ())

	// case OTARRAY, OTMAP, OTCHAN, OTSTRUCT, OTINTER, OTFUNC:
	//      unreachable - should have been resolved by typechecking

	// case OCLOSURE:
	//	unimplemented

	case OPTRLIT:
		n := p.expr()
		if !p.bool() /* !implicit, i.e. '&' operator */ {
			if n.Op == OCOMPLIT {
				// Special case for &T{...}: turn into (*T){...}.
				n.Right = nod(OIND, n.Right, nil)
				n.Right.Implicit = true
			} else {
				n = nod(OADDR, n, nil)
			}
		}
		return n

	case OSTRUCTLIT:
		n := nod(OCOMPLIT, nil, typenod(p.typ()))
		n.List.Set(p.elemList()) // special handling of field names
		return n

	// case OARRAYLIT, OSLICELIT, OMAPLIT:
	// 	unreachable - mapped to case OCOMPLIT below by exporter

	case OCOMPLIT:
		n := nod(OCOMPLIT, nil, typenod(p.typ()))
		n.List.Set(p.exprList())
		return n

	case OKEY:
		left, right := p.exprsOrNil()
		return nod(OKEY, left, right)

	// case OSTRUCTKEY:
	//	unreachable - handled in case OSTRUCTLIT by elemList

	// case OCALLPART:
	//	unimplemented

	// case OXDOT, ODOT, ODOTPTR, ODOTINTER, ODOTMETH:
	// 	unreachable - mapped to case OXDOT below by exporter

	case OXDOT:
		// see parser.new_dotname
		return nodSym(OXDOT, p.expr(), p.fieldSym())

	// case ODOTTYPE, ODOTTYPE2:
	// 	unreachable - mapped to case ODOTTYPE below by exporter

	case ODOTTYPE:
		n := nod(ODOTTYPE, p.expr(), nil)
		if p.bool() {
			n.Right = p.expr()
		} else {
			n.Right = typenod(p.typ())
		}
		return n

	// case OINDEX, OINDEXMAP, OSLICE, OSLICESTR, OSLICEARR, OSLICE3, OSLICE3ARR:
	// 	unreachable - mapped to cases below by exporter

	case OINDEX:
		return nod(op, p.expr(), p.expr())

	case OSLICE, OSLICE3:
		n := nod(op, p.expr(), nil)
		low, high := p.exprsOrNil()
		var max *Node
		if n.Op.IsSlice3() {
			max = p.expr()
		}
		n.SetSliceBounds(low, high, max)
		return n

	// case OCONV, OCONVIFACE, OCONVNOP, OARRAYBYTESTR, OARRAYRUNESTR, OSTRARRAYBYTE, OSTRARRAYRUNE, ORUNESTR:
	// 	unreachable - mapped to OCONV case below by exporter

	case OCONV:
		n := nod(OCALL, typenod(p.typ()), nil)
		n.List.Set(p.exprList())
		return n

	case OCOPY, OCOMPLEX, OREAL, OIMAG, OAPPEND, OCAP, OCLOSE, ODELETE, OLEN, OMAKE, ONEW, OPANIC, ORECOVER, OPRINT, OPRINTN:
		n := builtinCall(op)
		n.List.Set(p.exprList())
		if op == OAPPEND {
			n.Isddd = p.bool()
		}
		return n

	// case OCALL, OCALLFUNC, OCALLMETH, OCALLINTER, OGETG:
	// 	unreachable - mapped to OCALL case below by exporter

	case OCALL:
		n := nod(OCALL, p.expr(), nil)
		n.List.Set(p.exprList())
		n.Isddd = p.bool()
		return n

	case OMAKEMAP, OMAKECHAN, OMAKESLICE:
		n := builtinCall(OMAKE)
		n.List.Append(typenod(p.typ()))
		n.List.Append(p.exprList()...)
		return n

	// unary expressions
	case OPLUS, OMINUS, OADDR, OCOM, OIND, ONOT, ORECV:
		return nod(op, p.expr(), nil)

	// binary expressions
	case OADD, OAND, OANDAND, OANDNOT, ODIV, OEQ, OGE, OGT, OLE, OLT,
		OLSH, OMOD, OMUL, ONE, OOR, OOROR, ORSH, OSEND, OSUB, OXOR:
		return nod(op, p.expr(), p.expr())

	case OADDSTR:
		list := p.exprList()
		x := list[0]
		for _, y := range list[1:] {
			x = nod(OADD, x, y)
		}
		return x

	// case OCMPSTR, OCMPIFACE:
	// 	unreachable - mapped to std comparison operators by exporter

	case ODCLCONST:
		// TODO(gri) these should not be exported in the first place
		return nod(OEMPTY, nil, nil)

	// --------------------------------------------------------------------
	// statements
	case ODCL:
		if p.version < 2 {
			// versions 0 and 1 exported a bool here but it
			// was always false - simply ignore in this case
			p.bool()
		}
		lhs := dclname(p.sym())
		typ := typenod(p.typ())
		return liststmt(variter([]*Node{lhs}, typ, nil)) // TODO(gri) avoid list creation

	// case ODCLFIELD:
	//	unimplemented

	// case OAS, OASWB:
	// 	unreachable - mapped to OAS case below by exporter

	case OAS:
		return nod(OAS, p.expr(), p.expr())

	case OASOP:
		n := nod(OASOP, nil, nil)
		n.Etype = EType(p.int())
		n.Left = p.expr()
		if !p.bool() {
			n.Right = nodintconst(1)
			n.Implicit = true
		} else {
			n.Right = p.expr()
		}
		return n

	// case OAS2DOTTYPE, OAS2FUNC, OAS2MAPR, OAS2RECV:
	// 	unreachable - mapped to OAS2 case below by exporter

	case OAS2:
		n := nod(OAS2, nil, nil)
		n.List.Set(p.exprList())
		n.Rlist.Set(p.exprList())
		return n

	case ORETURN:
		n := nod(ORETURN, nil, nil)
		n.List.Set(p.exprList())
		return n

	// case ORETJMP:
	// 	unreachable - generated by compiler for trampolin routines (not exported)

	case OPROC, ODEFER:
		return nod(op, p.expr(), nil)

	case OIF:
		markdcl()
		n := nod(OIF, nil, nil)
		n.Ninit.Set(p.stmtList())
		n.Left = p.expr()
		n.Nbody.Set(p.stmtList())
		n.Rlist.Set(p.stmtList())
		popdcl()
		return n

	case OFOR:
		markdcl()
		n := nod(OFOR, nil, nil)
		n.Ninit.Set(p.stmtList())
		n.Left, n.Right = p.exprsOrNil()
		n.Nbody.Set(p.stmtList())
		popdcl()
		return n

	case ORANGE:
		markdcl()
		n := nod(ORANGE, nil, nil)
		n.List.Set(p.stmtList())
		n.Right = p.expr()
		n.Nbody.Set(p.stmtList())
		popdcl()
		return n

	case OSELECT, OSWITCH:
		markdcl()
		n := nod(op, nil, nil)
		n.Ninit.Set(p.stmtList())
		n.Left, _ = p.exprsOrNil()
		n.List.Set(p.stmtList())
		popdcl()
		return n

	// case OCASE, OXCASE:
	// 	unreachable - mapped to OXCASE case below by exporter

	case OXCASE:
		markdcl()
		n := nod(OXCASE, nil, nil)
		n.Xoffset = int64(block)
		n.List.Set(p.exprList())
		// TODO(gri) eventually we must declare variables for type switch
		// statements (type switch statements are not yet exported)
		n.Nbody.Set(p.stmtList())
		popdcl()
		return n

	// case OFALL:
	// 	unreachable - mapped to OXFALL case below by exporter

	case OXFALL:
		n := nod(OXFALL, nil, nil)
		n.Xoffset = int64(block)
		return n

	case OBREAK, OCONTINUE:
		left, _ := p.exprsOrNil()
		if left != nil {
			left = newname(left.Sym)
		}
		return nod(op, left, nil)

	// case OEMPTY:
	// 	unreachable - not emitted by exporter

	case OGOTO, OLABEL:
		n := nod(op, newname(p.expr().Sym), nil)
		n.Sym = dclstack // context, for goto restrictions
		return n

	case OEND:
		return nil

	default:
		Fatalf("cannot import %v (%d) node\n"+
			"==> please file an issue and assign to gri@\n", op, int(op))
		panic("unreachable") // satisfy compiler
	}
}

func builtinCall(op Op) *Node {
	return nod(OCALL, mkname(builtinpkg.Lookup(goopnames[op])), nil)
}

func (p *importer) exprsOrNil() (a, b *Node) {
	ab := p.int()
	if ab&1 != 0 {
		a = p.expr()
	}
	if ab&2 != 0 {
		b = p.expr()
	}
	return
}

func (p *importer) fieldSym() *Sym {
	name := p.string()
	pkg := localpkg
	if !exportname(name) {
		pkg = p.pkg()
	}
	return pkg.Lookup(name)
}

func (p *importer) sym() *Sym {
	name := p.string()
	pkg := localpkg
	if name != "_" {
		pkg = p.pkg()
	}
	return pkg.Lookup(name)
}

func (p *importer) bool() bool {
	return p.int() != 0
}

func (p *importer) op() Op {
	return Op(p.int())
}

// ----------------------------------------------------------------------------
// Low-level decoders

func (p *importer) tagOrIndex() int {
	if p.debugFormat {
		p.marker('t')
	}

	return int(p.rawInt64())
}

func (p *importer) int() int {
	x := p.int64()
	if int64(int(x)) != x {
		formatErrorf("exported integer too large")
	}
	return int(x)
}

func (p *importer) int64() int64 {
	if p.debugFormat {
		p.marker('i')
	}

	return p.rawInt64()
}

func (p *importer) string() string {
	if p.debugFormat {
		p.marker('s')
	}
	// if the string was seen before, i is its index (>= 0)
	// (the empty string is at index 0)
	i := p.rawInt64()
	if i >= 0 {
		return p.strList[i]
	}
	// otherwise, i is the negative string length (< 0)
	if n := int(-i); n <= cap(p.buf) {
		p.buf = p.buf[:n]
	} else {
		p.buf = make([]byte, n)
	}
	for i := range p.buf {
		p.buf[i] = p.rawByte()
	}
	s := string(p.buf)
	p.strList = append(p.strList, s)
	return s
}

func (p *importer) marker(want byte) {
	if got := p.rawByte(); got != want {
		formatErrorf("incorrect marker: got %c; want %c (pos = %d)", got, want, p.read)
	}

	pos := p.read
	if n := int(p.rawInt64()); n != pos {
		formatErrorf("incorrect position: got %d; want %d", n, pos)
	}
}

// rawInt64 should only be used by low-level decoders.
func (p *importer) rawInt64() int64 {
	i, err := binary.ReadVarint(p)
	if err != nil {
		formatErrorf("read error: %v", err)
	}
	return i
}

// rawStringln should only be used to read the initial version string.
func (p *importer) rawStringln(b byte) string {
	p.buf = p.buf[:0]
	for b != '\n' {
		p.buf = append(p.buf, b)
		b = p.rawByte()
	}
	return string(p.buf)
}

// needed for binary.ReadVarint in rawInt64
func (p *importer) ReadByte() (byte, error) {
	return p.rawByte(), nil
}

// rawByte is the bottleneck interface for reading from p.in.
// It unescapes '|' 'S' to '$' and '|' '|' to '|'.
// rawByte should only be used by low-level decoders.
func (p *importer) rawByte() byte {
	c, err := p.in.ReadByte()
	p.read++
	if err != nil {
		formatErrorf("read error: %v", err)
	}
	if c == '|' {
		c, err = p.in.ReadByte()
		p.read++
		if err != nil {
			formatErrorf("read error: %v", err)
		}
		switch c {
		case 'S':
			c = '$'
		case '|':
			// nothing to do
		default:
			formatErrorf("unexpected escape sequence in export data")
		}
	}
	return c
}
