// 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"
	"cmd/compile/internal/types"
	"cmd/internal/src"
	"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
	imp     *types.Pkg // imported package
	buf     []byte     // reused for reading strings
	version int        // export format version

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

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

	// position encoding
	posInfoFormat bool
	prevFile      string
	prevLine      int
	posBase       *src.PosBase

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

// Import populates imp from the serialized package data read from in.
func Import(imp *types.Pkg, in *bufio.Reader) {
	inimport = true
	defer func() { inimport = false }()

	p := importer{
		in:       in,
		imp:      imp,
		version:  -1,           // unknown version
		strList:  []string{""}, // empty string is mapped to 0
		pathList: []string{""}, // empty path 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 7:
	// 	...
	//	fallthrough
	case 6, 5, 4, 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:
		p.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 {
		p.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 {
		p.formatErrorf("got %d objects; want %d", objcount, count)
	}

	// read inlineable functions bodies
	if dclcontext != PEXTERN {
		p.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 {
			p.formatErrorf("index not increasing: %d <= %d", i, i0)
		}
		i0 = i

		if Curfn != nil {
			p.formatErrorf("unexpected Curfn %v", Curfn)
		}

		// 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.

		inlCost := p.int()
		if f := p.funcList[i]; f != nil && f.Func.Inl == nil {
			// function not yet imported - read body and set it
			funchdr(f)
			body := p.stmtList()
			funcbody()
			f.Func.Inl = &Inline{
				Cost: int32(inlCost),
				Body: body,
			}
			importlist = append(importlist, f)
			if Debug['E'] > 0 && Debug['m'] > 2 {
				if Debug['m'] > 3 {
					fmt.Printf("inl body for %v: %+v\n", f, asNodes(body))
				} else {
					fmt.Printf("inl body for %v: %v\n", f, asNodes(body))
				}
			}
		} 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 {
		p.formatErrorf("got %d functions; want %d", objcount, count)
	}

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

	p.verifyTypes()

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

	typecheckok = tcok
	resumecheckwidth()

	if debug_dclstack != 0 {
		testdclstack()
	}
}

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

	yyerror("cannot import %q due to version skew - reinstall package (%s)",
		p.imp.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) {
			p.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, p.imp.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() *types.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 {
		p.formatErrorf("expected package tag, found tag = %d", i)
	}

	// read package data
	name := p.string()
	var path string
	if p.version >= 5 {
		path = p.path()
	} else {
		path = p.string()
	}
	var height int
	if p.version >= 6 {
		height = p.int()
	}

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

	// we should never see a bad import path
	if isbadimport(path, true) {
		p.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) {
		p.formatErrorf("package path %q for pkg index %d", path, len(p.pkgList))
	}

	if p.version >= 6 {
		if height < 0 || height >= types.MaxPkgHeight {
			p.formatErrorf("bad package height %v for package %s", height, name)
		}

		// reexported packages should always have a lower height than
		// the main package
		if len(p.pkgList) != 0 && height >= p.imp.Height {
			p.formatErrorf("package %q (height %d) reexports package %q (height %d)", p.imp.Path, p.imp.Height, path, height)
		}
	}

	// add package to pkgList
	pkg := p.imp
	if path != "" {
		pkg = types.NewPkg(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)", p.imp.Path, path)
		errorexit()
	}
	pkg.Height = height
	p.pkgList = append(p.pkgList, pkg)

	return pkg
}

func idealType(typ *types.Type) *types.Type {
	switch typ {
	case types.Idealint, types.Idealrune, types.Idealfloat, types.Idealcomplex:
		// canonicalize ideal types
		typ = types.Types[TIDEAL]
	}
	return typ
}

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

	case aliasTag:
		pos := p.pos()
		sym := p.qualifiedName()
		typ := p.typ()
		importalias(p.imp, pos, sym, typ)

	case typeTag:
		p.typ()

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

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

		sig := functypefield(nil, params, result)
		importfunc(p.imp, pos, sym, sig)
		p.funcList = append(p.funcList, asNode(sym.Def))

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

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

	file := p.prevFile
	line := p.prevLine
	delta := p.int()
	line += delta
	if p.version >= 5 {
		if delta == deltaNewFile {
			if n := p.int(); n >= 0 {
				// file changed
				file = p.path()
				line = n
			}
		}
	} else {
		if delta == 0 {
			if n := p.int(); n >= 0 {
				// file changed
				file = p.prevFile[:n] + p.string()
				line = p.int()
			}
		}
	}
	if file != p.prevFile {
		p.prevFile = file
		p.posBase = src.NewFileBase(file, file)
	}
	p.prevLine = line

	pos := src.MakePos(p.posBase, uint(line), 0)
	xpos := Ctxt.PosTable.XPos(pos)
	return xpos
}

func (p *importer) path() string {
	// if the path was seen before, i is its index (>= 0)
	// (the empty string is at index 0)
	i := p.int()
	if i >= 0 {
		return p.pathList[i]
	}
	// otherwise, i is the negative path length (< 0)
	a := make([]string, -i)
	for n := range a {
		a[n] = p.string()
	}
	s := strings.Join(a, "/")
	p.pathList = append(p.pathList, s)
	return s
}

func (p *importer) newtyp(etype types.EType) *types.Type {
	t := types.New(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 *types.Type) {
	if pt.Etype == TFORW {
		copytype(typenod(pt), t)
		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 *types.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, p.imp.Path)
		}
	}

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

func (p *importer) typ() *types.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 *types.Type
	switch i {
	case namedTag:
		pos := p.pos()
		tsym := p.qualifiedName()

		t = importtype(p.imp, pos, tsym)
		p.typList = append(p.typList, t)
		dup := !t.IsKind(types.TFORW) // type already imported

		// 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-- {
			mpos := p.pos()
			sym := p.fieldSym()

			// during import unexported method names should be in the type's package
			if !types.IsExported(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()

			mt := functypefield(recv[0], params, result)
			oldm := addmethod(sym, mt, false, nointerface)

			if dup {
				// An earlier import already declared this type and its methods.
				// Discard the duplicate method declaration.
				n := asNode(oldm.Type.Nname())
				p.funcList = append(p.funcList, n)
				continue
			}

			n := newfuncnamel(mpos, methodSym(recv[0].Type, sym))
			n.Type = mt
			n.SetClass(PFUNC)
			checkwidth(n.Type)
			p.funcList = append(p.funcList, 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.
			mt.SetNname(asTypesNode(n))

			if Debug['E'] > 0 {
				fmt.Printf("import [%q] meth %v \n", p.imp.Path, n)
			}
		}

		dclcontext = savedContext

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

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

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

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

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

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

	case interfaceTag:
		if ml := p.methodList(); len(ml) == 0 {
			t = types.Types[TINTER]
		} else {
			t = p.newtyp(TINTER)
			t.SetInterface(ml)
		}

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

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

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

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

	return t
}

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

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

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

	f := types.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
	} else if alias {
		// anonymous field: we have an explicit name because it's a type alias
		f.Embedded = 1
	}

	f.Pos = pos
	f.Sym = sym
	f.Type = typ
	f.Note = note

	return f
}

func (p *importer) methodList() (methods []*types.Field) {
	for n := p.int(); n > 0; n-- {
		f := types.NewField()
		f.Pos = p.pos()
		f.Type = p.typ()
		methods = append(methods, f)
	}

	for n := p.int(); n > 0; n-- {
		methods = append(methods, p.method())
	}

	return
}

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

	f := types.NewField()
	f.Pos = pos
	f.Sym = sym
	f.Type = functypefield(fakeRecvField(), params, result)
	return f
}

func (p *importer) fieldName() (*types.Sym, bool) {
	name := p.string()
	if p.version == 0 && name == "_" {
		// version 0 didn't export a package for _ field names
		// but used the builtin package instead
		return builtinpkg.Lookup(name), false
	}
	pkg := localpkg
	alias := false
	switch name {
	case "":
		// 1) field name matches base type name and is exported: nothing to do
	case "?":
		// 2) field name matches base type name and is not exported: need package
		name = ""
		pkg = p.pkg()
	case "@":
		// 3) field name doesn't match base type name (alias name): need name and possibly package
		name = p.string()
		alias = true
		fallthrough
	default:
		if !types.IsExported(name) {
			pkg = p.pkg()
		}
	}
	return pkg.Lookup(name), alias
}

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

func (p *importer) paramList() []*types.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([]*types.Field, i)
	for i := range fs {
		fs[i] = p.param(named)
	}
	return fs
}

func (p *importer) param(named bool) *types.Field {
	f := types.NewField()
	// TODO(mdempsky): Need param position.
	f.Pos = lineno
	f.Type = p.typ()
	if f.Type.Etype == TDDDFIELD {
		// TDDDFIELD indicates wrapped ... slice type
		f.Type = types.NewSlice(f.Type.DDDField())
		f.SetIsddd(true)
	}

	if named {
		name := p.string()
		if name == "" {
			p.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)
	}

	// 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 *types.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 == types.Idealrune
		x.U = u

	case floatTag:
		f := newMpflt()
		p.float(f)
		if typ == types.Idealint || typ.IsInteger() || typ.IsPtr() || typ.IsUnsafePtr() {
			// uncommon case: large int encoded as float
			//
			// This happens for unsigned typed integers
			// and (on 64-bit platforms) pointers because
			// of values in the range [2^63, 2^64).
			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:
		p.formatErrorf("unknown constant (importing package with errors)")

	case nilTag:
		x.U = new(NilVal)

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

	// verify ideal type
	if typ.IsUntyped() && untype(x.Ctype()) != typ {
		p.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
}

func npos(pos src.XPos, n *Node) *Node {
	n.Pos = pos
	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:
		pos := p.pos()
		typ := p.typ()
		n := npos(pos, nodlit(p.value(typ)))
		n.Type = idealType(typ)
		return n

	case ONAME:
		return npos(p.pos(), mkname(p.sym()))

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

	case OTYPE:
		return npos(p.pos(), typenod(p.typ()))

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

	// case OCLOSURE:
	//	unimplemented

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

	case OSTRUCTLIT:
		// TODO(mdempsky): Export position information for OSTRUCTKEY nodes.
		savedlineno := lineno
		lineno = p.pos()
		n := nodl(lineno, OCOMPLIT, nil, typenod(p.typ()))
		n.List.Set(p.elemList()) // special handling of field names
		lineno = savedlineno
		return n

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

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

	case OKEY:
		pos := p.pos()
		left, right := p.exprsOrNil()
		return nodl(pos, 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 npos(p.pos(), nodSym(OXDOT, p.expr(), p.fieldSym()))

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

	case ODOTTYPE:
		n := nodl(p.pos(), ODOTTYPE, p.expr(), nil)
		n.Type = p.typ()
		return n

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

	case OINDEX:
		return nodl(p.pos(), op, p.expr(), p.expr())

	case OSLICE, OSLICE3:
		n := nodl(p.pos(), 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 := nodl(p.pos(), OCONV, p.expr(), nil)
		n.Type = p.typ()
		return n

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

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

	case OCALL:
		n := nodl(p.pos(), OCALL, p.expr(), nil)
		n.List.Set(p.exprList())
		n.SetIsddd(p.bool())
		return n

	case OMAKEMAP, OMAKECHAN, OMAKESLICE:
		n := npos(p.pos(), 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 nodl(p.pos(), 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 nodl(p.pos(), op, p.expr(), p.expr())

	case OADDSTR:
		pos := p.pos()
		list := p.exprList()
		x := npos(pos, list[0])
		for _, y := range list[1:] {
			x = nodl(pos, 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 nodl(p.pos(), 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()
		}
		pos := p.pos()
		lhs := npos(pos, dclname(p.sym()))
		typ := typenod(p.typ())
		return npos(pos, 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 nodl(p.pos(), OAS, p.expr(), p.expr())

	case OASOP:
		n := nodl(p.pos(), OASOP, nil, nil)
		n.SetSubOp(p.op())
		n.Left = p.expr()
		if !p.bool() {
			n.Right = nodintconst(1)
			n.SetImplicit(true)
		} else {
			n.Right = p.expr()
		}
		return n

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

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

	case ORETURN:
		n := nodl(p.pos(), 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 nodl(p.pos(), op, p.expr(), nil)

	case OIF:
		n := nodl(p.pos(), OIF, nil, nil)
		n.Ninit.Set(p.stmtList())
		n.Left = p.expr()
		n.Nbody.Set(p.stmtList())
		n.Rlist.Set(p.stmtList())
		return n

	case OFOR:
		n := nodl(p.pos(), OFOR, nil, nil)
		n.Ninit.Set(p.stmtList())
		n.Left, n.Right = p.exprsOrNil()
		n.Nbody.Set(p.stmtList())
		return n

	case ORANGE:
		n := nodl(p.pos(), ORANGE, nil, nil)
		n.List.Set(p.stmtList())
		n.Right = p.expr()
		n.Nbody.Set(p.stmtList())
		return n

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

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

	case OXCASE:
		n := nodl(p.pos(), OXCASE, nil, nil)
		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())
		return n

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

	case OFALL:
		n := nodl(p.pos(), OFALL, nil, nil)
		return n

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

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

	case OGOTO, OLABEL:
		return nodl(p.pos(), op, newname(p.expr().Sym), nil)

	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.node()
	}
	return
}

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

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

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 {
		p.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 {
		p.formatErrorf("incorrect marker: got %c; want %c (pos = %d)", got, want, p.read)
	}

	pos := p.read
	if n := int(p.rawInt64()); n != pos {
		p.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 {
		p.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 {
		p.formatErrorf("read error: %v", err)
	}
	if c == '|' {
		c, err = p.in.ReadByte()
		p.read++
		if err != nil {
			p.formatErrorf("read error: %v", err)
		}
		switch c {
		case 'S':
			c = '$'
		case '|':
			// nothing to do
		default:
			p.formatErrorf("unexpected escape sequence in export data")
		}
	}
	return c
}
