package interp

import (
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
)

// Interpreter node structure for AST and CFG.
type node struct {
	debug      *nodeDebugData // debug info
	child      []*node        // child subtrees (AST)
	anc        *node          // ancestor (AST)
	param      []*itype       // generic parameter nodes (AST)
	start      *node          // entry point in subtree (CFG)
	tnext      *node          // true branch successor (CFG)
	fnext      *node          // false branch successor (CFG)
	interp     *Interpreter   // interpreter context
	index      int64          // node index (dot display)
	findex     int            // index of value in frame or frame size (func def, type def)
	level      int            // number of frame indirections to access value
	nleft      int            // number of children in left part (assign) or indicates preceding type (compositeLit)
	nright     int            // number of children in right part (assign)
	kind       nkind          // kind of node
	pos        token.Pos      // position in source code, relative to fset
	sym        *symbol        // associated symbol
	typ        *itype         // type of value in frame, or nil
	recv       *receiver      // method receiver node for call, or nil
	types      []reflect.Type // frame types, used by function literals only
	scope      *scope         // frame scope
	action     action         // action
	exec       bltn           // generated function to execute
	gen        bltnGenerator  // generator function to produce above bltn
	val        interface{}    // static generic value (CFG execution)
	rval       reflect.Value  // reflection value to let runtime access interpreter (CFG)
	ident      string         // set if node is a var or func
	redeclared bool           // set if node is a redeclared variable (CFG)
	meta       interface{}    // meta stores meta information between gta runs, like errors
}

func ( *node) () bool {
	if  == nil || .debug == nil {
		return false
	}

	if .debug.breakOnLine || .debug.breakOnCall {
		return true
	}

	return false
}

func ( *node) ( *Program) {
	if .debug == nil {
		.debug = new(nodeDebugData)
	}
	.debug.program = 
}

func ( *node) ( bool) {
	if .debug == nil {
		if ! {
			return
		}
		.debug = new(nodeDebugData)
	}
	.debug.breakOnCall = 
}

func ( *node) ( bool) {
	if .debug == nil {
		if ! {
			return
		}
		.debug = new(nodeDebugData)
	}
	.debug.breakOnLine = 
}

// receiver stores method receiver object access path.
type receiver struct {
	node  *node         // receiver value for alias and struct types
	val   reflect.Value // receiver value for interface type and value type
	index []int         // path in receiver value for interface or value type
}

// frame contains values for the current execution level (a function context).
type frame struct {
	// id is an atomic counter used for cancellation, only accessed
	// via newFrame/runid/setrunid/clone.
	// Located at start of struct to ensure proper alignment.
	id uint64

	debug *frameDebugData

	root *frame          // global space
	anc  *frame          // ancestor frame (caller space)
	data []reflect.Value // values

	mutex     sync.RWMutex
	deferred  [][]reflect.Value  // defer stack
	recovered interface{}        // to handle panic recover
	done      reflect.SelectCase // for cancellation of channel operations
}

func newFrame( *frame,  int,  uint64) *frame {
	 := &frame{
		anc:  ,
		data: make([]reflect.Value, ),
		id:   ,
	}
	if  == nil {
		.root = 
	} else {
		.done = .done
		.root = .root
	}
	return 
}

func ( *frame) () uint64      { return atomic.LoadUint64(&.id) }
func ( *frame) ( uint64) { atomic.StoreUint64(&.id, ) }
func ( *frame) () *frame {
	.mutex.RLock()
	defer .mutex.RUnlock()
	 := &frame{
		anc:       .anc,
		root:      .root,
		deferred:  .deferred,
		recovered: .recovered,
		id:        .runid(),
		done:      .done,
		debug:     .debug,
	}
	.data = make([]reflect.Value, len(.data))
	copy(.data, .data)
	return 
}

// Exports stores the map of binary packages per package path.
// The package path is the path joined from the import path and the package name
// as specified in source files by the "package" statement.
type Exports map[string]map[string]reflect.Value

// imports stores the map of source packages per package path.
type imports map[string]map[string]*symbol

// opt stores interpreter options.
type opt struct {
	// dotCmd is the command to process the dot graph produced when astDot and/or
	// cfgDot is enabled. It defaults to 'dot -Tdot -o <filename>.dot'.
	dotCmd       string
	context      build.Context     // build context: GOPATH, build constraints
	stdin        io.Reader         // standard input
	stdout       io.Writer         // standard output
	stderr       io.Writer         // standard error
	args         []string          // cmdline args
	env          map[string]string // environment of interpreter, entries in form of "key=value"
	filesystem   fs.FS             // filesystem containing sources
	astDot       bool              // display AST graph (debug)
	cfgDot       bool              // display CFG graph (debug)
	noRun        bool              // compile, but do not run
	fastChan     bool              // disable cancellable chan operations
	specialStdio bool              // allows os.Stdin, os.Stdout, os.Stderr to not be file descriptors
	unrestricted bool              // allow use of non-sandboxed symbols
}

// Interpreter contains global resources and state.
type Interpreter struct {
	// id is an atomic counter used for run cancellation,
	// only accessed via runid/stop
	// Located at start of struct to ensure proper alignment on 32-bit
	// architectures.
	id uint64

	// nindex is a node number incremented for each new node.
	// It is used for debug (AST and CFG graphs). As it is atomically
	// incremented, keep it aligned on 64 bits boundary.
	nindex int64

	name string // name of the input source file (or main)

	opt                                         // user settable options
	cancelChan bool                             // enables cancellable chan operations
	fset       *token.FileSet                   // fileset to locate node in source code
	binPkg     Exports                          // binary packages used in interpreter, indexed by path
	rdir       map[string]bool                  // for src import cycle detection
	mapTypes   map[reflect.Value][]reflect.Type // special interfaces mapping for wrappers

	mutex    sync.RWMutex
	frame    *frame            // program data storage during execution
	universe *scope            // interpreter global level scope
	scopes   map[string]*scope // package level scopes, indexed by import path
	srcPkg   imports           // source packages used in interpreter, indexed by path
	pkgNames map[string]string // package names, indexed by import path
	done     chan struct{}     // for cancellation of channel operations
	roots    []*node
	generic  map[string]*node

	hooks *hooks // symbol hooks

	debugger *Debugger
}

const (
	mainID     = "main"
	selfPrefix = "github.com/traefik/yaegi"
	selfPath   = selfPrefix + "/interp/interp"
	// DefaultSourceName is the name used by default when the name of the input
	// source file has not been specified for an Eval.
	// TODO(mpl): something even more special as a name?
	DefaultSourceName = "_.go"

	// Test is the value to pass to EvalPath to activate evaluation of test functions.
	Test = false
	// NoTest is the value to pass to EvalPath to skip evaluation of test functions.
	NoTest = true
)

// Self points to the current interpreter if accessed from within itself, or is nil.
var Self *Interpreter

// Symbols exposes interpreter values.
var Symbols = Exports{
	selfPath: map[string]reflect.Value{
		"New": reflect.ValueOf(New),

		"Interpreter": reflect.ValueOf((*Interpreter)(nil)),
		"Options":     reflect.ValueOf((*Options)(nil)),
		"Panic":       reflect.ValueOf((*Panic)(nil)),
	},
}

func init() { Symbols[selfPath]["Symbols"] = reflect.ValueOf(Symbols) }

// _error is a wrapper of error interface type.
type _error struct {
	IValue interface{}
	WError func() string
}

func ( _error) () string { return .WError() }

// Panic is an error recovered from a panic call in interpreted code.
type Panic struct {
	// Value is the recovered value of a call to panic.
	Value interface{}

	// Callers is the call stack obtained from the recover call.
	// It may be used as the parameter to runtime.CallersFrames.
	Callers []uintptr

	// Stack is the call stack buffer for debug.
	Stack []byte
}

// TODO: Capture interpreter stack frames also and remove
// fmt.Fprintln(n.interp.stderr, oNode.cfgErrorf("panic")) in runCfg.

func ( Panic) () string { return fmt.Sprint(.Value) }

// Walk traverses AST n in depth first order, call cbin function
// at node entry and cbout function at node exit.
func ( *node) ( func( *node) bool,  func( *node)) {
	if  != nil && !() {
		return
	}
	for ,  := range .child {
		.(, )
	}
	if  != nil {
		()
	}
}

// Options are the interpreter options.
type Options struct {
	// GoPath sets GOPATH for the interpreter.
	GoPath string

	// BuildTags sets build constraints for the interpreter.
	BuildTags []string

	// Standard input, output and error streams.
	// They default to os.Stdin, os.Stdout and os.Stderr respectively.
	Stdin          io.Reader
	Stdout, Stderr io.Writer

	// Cmdline args, defaults to os.Args.
	Args []string

	// Environment of interpreter. Entries are in the form "key=values".
	Env []string

	// SourcecodeFilesystem is where the _sourcecode_ is loaded from and does
	// NOT affect the filesystem of scripts when they run.
	// It can be any fs.FS compliant filesystem (e.g. embed.FS, or fstest.MapFS for testing)
	// See example/fs/fs_test.go for an example.
	SourcecodeFilesystem fs.FS

	// Unrestricted allows to run non sandboxed stdlib symbols such as os/exec and environment
	Unrestricted bool
}

// New returns a new interpreter.
func ( Options) *Interpreter {
	 := Interpreter{
		opt:      opt{context: build.Default, filesystem: &realFS{}, env: map[string]string{}},
		frame:    newFrame(nil, 0, 0),
		fset:     token.NewFileSet(),
		universe: initUniverse(),
		scopes:   map[string]*scope{},
		binPkg:   Exports{"": map[string]reflect.Value{"_error": reflect.ValueOf((*_error)(nil))}},
		mapTypes: map[reflect.Value][]reflect.Type{},
		srcPkg:   imports{},
		pkgNames: map[string]string{},
		rdir:     map[string]bool{},
		hooks:    &hooks{},
		generic:  map[string]*node{},
	}

	if .opt.stdin = .Stdin; .opt.stdin == nil {
		.opt.stdin = os.Stdin
	}

	if .opt.stdout = .Stdout; .opt.stdout == nil {
		.opt.stdout = os.Stdout
	}

	if .opt.stderr = .Stderr; .opt.stderr == nil {
		.opt.stderr = os.Stderr
	}

	if .opt.args = .Args; .opt.args == nil {
		.opt.args = os.Args
	}

	// unrestricted allows to use non sandboxed stdlib symbols and env.
	if .Unrestricted {
		.opt.unrestricted = true
	} else {
		for ,  := range .Env {
			 := strings.SplitN(, "=", 2)
			if len() == 2 {
				.opt.env[[0]] = [1]
			} else {
				.opt.env[[0]] = ""
			}
		}
	}

	if .SourcecodeFilesystem != nil {
		.opt.filesystem = .SourcecodeFilesystem
	}

	.opt.context.GOPATH = .GoPath
	if len(.BuildTags) > 0 {
		.opt.context.BuildTags = .BuildTags
	}

	// astDot activates AST graph display for the interpreter
	.opt.astDot, _ = strconv.ParseBool(os.Getenv("YAEGI_AST_DOT"))

	// cfgDot activates CFG graph display for the interpreter
	.opt.cfgDot, _ = strconv.ParseBool(os.Getenv("YAEGI_CFG_DOT"))

	// dotCmd defines how to process the dot code generated whenever astDot and/or
	// cfgDot is enabled. It defaults to 'dot -Tdot -o<filename>.dot' where filename
	// is context dependent.
	.opt.dotCmd = os.Getenv("YAEGI_DOT_CMD")

	// noRun disables the execution (but not the compilation) in the interpreter
	.opt.noRun, _ = strconv.ParseBool(os.Getenv("YAEGI_NO_RUN"))

	// fastChan disables the cancellable version of channel operations in evalWithContext
	.opt.fastChan, _ = strconv.ParseBool(os.Getenv("YAEGI_FAST_CHAN"))

	// specialStdio allows to assign directly io.Writer and io.Reader to os.Stdxxx,
	// even if they are not file descriptors.
	.opt.specialStdio, _ = strconv.ParseBool(os.Getenv("YAEGI_SPECIAL_STDIO"))

	return &
}

const (
	bltnAlignof  = "unsafe.Alignof"
	bltnAppend   = "append"
	bltnCap      = "cap"
	bltnClose    = "close"
	bltnComplex  = "complex"
	bltnImag     = "imag"
	bltnCopy     = "copy"
	bltnDelete   = "delete"
	bltnLen      = "len"
	bltnMake     = "make"
	bltnNew      = "new"
	bltnOffsetof = "unsafe.Offsetof"
	bltnPanic    = "panic"
	bltnPrint    = "print"
	bltnPrintln  = "println"
	bltnReal     = "real"
	bltnRecover  = "recover"
	bltnSizeof   = "unsafe.Sizeof"
)

func initUniverse() *scope {
	 := &scope{global: true, sym: map[string]*symbol{
		// predefined Go types
		"any":         {kind: typeSym, typ: &itype{cat: interfaceT, str: "any"}},
		"bool":        {kind: typeSym, typ: &itype{cat: boolT, name: "bool", str: "bool"}},
		"byte":        {kind: typeSym, typ: &itype{cat: uint8T, name: "uint8", str: "uint8"}},
		"comparable":  {kind: typeSym, typ: &itype{cat: comparableT, name: "comparable", str: "comparable"}},
		"complex64":   {kind: typeSym, typ: &itype{cat: complex64T, name: "complex64", str: "complex64"}},
		"complex128":  {kind: typeSym, typ: &itype{cat: complex128T, name: "complex128", str: "complex128"}},
		"error":       {kind: typeSym, typ: &itype{cat: errorT, name: "error", str: "error"}},
		"float32":     {kind: typeSym, typ: &itype{cat: float32T, name: "float32", str: "float32"}},
		"float64":     {kind: typeSym, typ: &itype{cat: float64T, name: "float64", str: "float64"}},
		"int":         {kind: typeSym, typ: &itype{cat: intT, name: "int", str: "int"}},
		"int8":        {kind: typeSym, typ: &itype{cat: int8T, name: "int8", str: "int8"}},
		"int16":       {kind: typeSym, typ: &itype{cat: int16T, name: "int16", str: "int16"}},
		"int32":       {kind: typeSym, typ: &itype{cat: int32T, name: "int32", str: "int32"}},
		"int64":       {kind: typeSym, typ: &itype{cat: int64T, name: "int64", str: "int64"}},
		"interface{}": {kind: typeSym, typ: &itype{cat: interfaceT, str: "interface{}"}},
		"rune":        {kind: typeSym, typ: &itype{cat: int32T, name: "int32", str: "int32"}},
		"string":      {kind: typeSym, typ: &itype{cat: stringT, name: "string", str: "string"}},
		"uint":        {kind: typeSym, typ: &itype{cat: uintT, name: "uint", str: "uint"}},
		"uint8":       {kind: typeSym, typ: &itype{cat: uint8T, name: "uint8", str: "uint8"}},
		"uint16":      {kind: typeSym, typ: &itype{cat: uint16T, name: "uint16", str: "uint16"}},
		"uint32":      {kind: typeSym, typ: &itype{cat: uint32T, name: "uint32", str: "uint32"}},
		"uint64":      {kind: typeSym, typ: &itype{cat: uint64T, name: "uint64", str: "uint64"}},
		"uintptr":     {kind: typeSym, typ: &itype{cat: uintptrT, name: "uintptr", str: "uintptr"}},

		// predefined Go constants
		"false": {kind: constSym, typ: untypedBool(nil), rval: reflect.ValueOf(false)},
		"true":  {kind: constSym, typ: untypedBool(nil), rval: reflect.ValueOf(true)},
		"iota":  {kind: constSym, typ: untypedInt(nil)},

		// predefined Go zero value
		"nil": {typ: &itype{cat: nilT, untyped: true, str: "nil"}},

		// predefined Go builtins
		bltnAppend:  {kind: bltnSym, builtin: _append},
		bltnCap:     {kind: bltnSym, builtin: _cap},
		bltnClose:   {kind: bltnSym, builtin: _close},
		bltnComplex: {kind: bltnSym, builtin: _complex},
		bltnImag:    {kind: bltnSym, builtin: _imag},
		bltnCopy:    {kind: bltnSym, builtin: _copy},
		bltnDelete:  {kind: bltnSym, builtin: _delete},
		bltnLen:     {kind: bltnSym, builtin: _len},
		bltnMake:    {kind: bltnSym, builtin: _make},
		bltnNew:     {kind: bltnSym, builtin: _new},
		bltnPanic:   {kind: bltnSym, builtin: _panic},
		bltnPrint:   {kind: bltnSym, builtin: _print},
		bltnPrintln: {kind: bltnSym, builtin: _println},
		bltnReal:    {kind: bltnSym, builtin: _real},
		bltnRecover: {kind: bltnSym, builtin: _recover},
	}}
	return 
}

// resizeFrame resizes the global frame of interpreter.
func ( *Interpreter) () {
	 := len(.universe.types)
	 := len(.frame.data)
	if - <= 0 {
		return
	}
	 := make([]reflect.Value, )
	copy(, .frame.data)
	for ,  := range .universe.types[:] {
		[+] = reflect.New().Elem()
	}
	.frame.data = 
}

// Eval evaluates Go code represented as a string. Eval returns the last result
// computed by the interpreter, and a non nil error in case of failure.
func ( *Interpreter) ( string) ( reflect.Value,  error) {
	return .eval(, "", true)
}

// EvalPath evaluates Go code located at path and returns the last result computed
// by the interpreter, and a non nil error in case of failure.
// The main function of the main package is executed if present.
func ( *Interpreter) ( string) ( reflect.Value,  error) {
	if !isFile(.opt.filesystem, ) {
		,  := .importSrc(mainID, , NoTest)
		return , 
	}

	,  := fs.ReadFile(.filesystem, )
	if  != nil {
		return , 
	}
	return .eval(string(), , false)
}

// EvalPathWithContext evaluates Go code located at path and returns the last
// result computed by the interpreter, and a non nil error in case of failure.
// The main function of the main package is executed if present.
func ( *Interpreter) ( context.Context,  string) ( reflect.Value,  error) {
	.mutex.Lock()
	.done = make(chan struct{})
	.cancelChan = !.opt.fastChan
	.mutex.Unlock()

	 := make(chan struct{})
	go func() {
		defer close()
		,  = .EvalPath()
	}()

	select {
	case <-.Done():
		.stop()
		return reflect.Value{}, .Err()
	case <-:
	}
	return , 
}

// EvalTest evaluates Go code located at path, including test files with "_test.go" suffix.
// A non nil error is returned in case of failure.
// The main function, test functions and benchmark functions are internally compiled but not
// executed. Test functions can be retrieved using the Symbol() method.
func ( *Interpreter) ( string) error {
	,  := .importSrc(mainID, , Test)
	return 
}

func isFile( fs.FS,  string) bool {
	,  := fs.Stat(, )
	return  == nil && .Mode().IsRegular()
}

func ( *Interpreter) (,  string,  bool) ( reflect.Value,  error) {
	,  := .compileSrc(, , )
	if  != nil {
		return , 
	}

	if .noRun {
		return , 
	}

	return .Execute()
}

// EvalWithContext evaluates Go code represented as a string. It returns
// a map on current interpreted package exported symbols.
func ( *Interpreter) ( context.Context,  string) (reflect.Value, error) {
	var  reflect.Value
	var  error

	.mutex.Lock()
	.done = make(chan struct{})
	.cancelChan = !.opt.fastChan
	.mutex.Unlock()

	 := make(chan struct{})
	go func() {
		defer func() {
			if  := recover();  != nil {
				var  [64]uintptr
				 := runtime.Callers(1, [:])
				 = Panic{Value: , Callers: [:], Stack: debug.Stack()}
			}
			close()
		}()
		,  = .Eval()
	}()

	select {
	case <-.Done():
		.stop()
		return reflect.Value{}, .Err()
	case <-:
	}
	return , 
}

// stop sends a semaphore to all running frames and closes the chan
// operation short circuit channel. stop may only be called once per
// invocation of EvalWithContext.
func ( *Interpreter) () {
	atomic.AddUint64(&.id, 1)
	close(.done)
}

func ( *Interpreter) () uint64 { return atomic.LoadUint64(&.id) }

// ignoreScannerError returns true if the error from Go scanner can be safely ignored
// to let the caller grab one more line before retrying to parse its input.
func ignoreScannerError( *scanner.Error,  string) bool {
	 := .Msg
	if strings.HasSuffix(, "found 'EOF'") {
		return true
	}
	if  == "raw string literal not terminated" {
		return true
	}
	if strings.HasPrefix(, "expected operand, found '}'") && !strings.HasSuffix(, "}") {
		return true
	}
	return false
}

// ImportUsed automatically imports pre-compiled packages included by Use().
// This is mainly useful for REPLs, or single command lines. In case of an ambiguous default
// package name, for example "rand" for crypto/rand and math/rand, the package name is
// constructed by replacing the last "/" by a "_", producing crypto_rand and math_rand.
// ImportUsed should not be called more than once, and not after a first Eval, as it may
// rename packages.
func ( *Interpreter) () {
	 := .universe
	for  := range .binPkg {
		// By construction, the package name is the last path element of the key.
		 := path.Base()
		if ,  := .sym[];  {
			// Handle collision by renaming old and new entries.
			 := key2name(fixKey(.typ.path))
			.sym[] = 
			if  !=  {
				delete(.sym, )
			}
			 = key2name(fixKey())
		}
		.sym[] = &symbol{kind: pkgSym, typ: &itype{cat: binPkgT, path: , scope: }}
	}
}

func key2name( string) string {
	return filepath.Join(, DefaultSourceName)
}

func fixKey( string) string {
	 := strings.LastIndex(, "/")
	if  >= 0 {
		 = [:] + "_" + [+1:]
	}
	return 
}

// REPL performs a Read-Eval-Print-Loop on input reader.
// Results are printed to the output writer of the Interpreter, provided as option
// at creation time. Errors are printed to the similarly defined errors writer.
// The last interpreter result value and error are returned.
func ( *Interpreter) () (reflect.Value, error) {
	, ,  := .stdin, .stdout, .stderr
	,  := context.WithCancel(context.Background())
	 := make(chan struct{})     // channel to terminate the REPL
	 := make(chan os.Signal, 1) // channel to trap interrupt signal (Ctrl-C)
	 := make(chan string)     // channel to read REPL input lines
	 := getPrompt(, )   // prompt activated on tty like IO stream
	 := bufio.NewScanner()      // read input stream line by line
	var  reflect.Value            // result value from eval
	var  error                  // error from eval
	 := ""                      // source string to evaluate

	signal.Notify(, os.Interrupt)
	defer signal.Stop()
	()

	go func() {
		defer close()
		for .Scan() {
			 <- .Text()
		}
		if  := .Err();  != nil {
			fmt.Fprintln(, )
		}
	}()

	go func() {
		for {
			select {
			case <-:
				()
				 <- ""
			case <-:
				return
			}
		}
	}()

	for {
		var  string

		select {
		case <-:
			()
			return , 
		case  = <-:
			 +=  + "\n"
		}

		,  = .EvalWithContext(, )
		if  != nil {
			switch e := .(type) {
			case scanner.ErrorList:
				if len() > 0 && ignoreScannerError([0], ) {
					continue
				}
				fmt.Fprintln(, strings.TrimPrefix([0].Error(), DefaultSourceName+":"))
			case Panic:
				fmt.Fprintln(, .Value)
				fmt.Fprintln(, string(.Stack))
			default:
				fmt.Fprintln(, )
			}
		}
		if errors.Is(, context.Canceled) {
			,  = context.WithCancel(context.Background())
		}
		 = ""
		()
	}
}

func doPrompt( io.Writer) func( reflect.Value) {
	return func( reflect.Value) {
		if .IsValid() {
			fmt.Fprintln(, ":", )
		}
		fmt.Fprint(, "> ")
	}
}

// getPrompt returns a function which prints a prompt only if input is a terminal.
func getPrompt( io.Reader,  io.Writer) func(reflect.Value) {
	,  := strconv.ParseBool(os.Getenv("YAEGI_PROMPT"))
	if  {
		return doPrompt()
	}
	,  := .(interface{ () (os.FileInfo, error) })
	if ! {
		return func(reflect.Value) {}
	}
	,  := .()
	if  == nil && .Mode()&os.ModeCharDevice != 0 {
		return doPrompt()
	}
	return func(reflect.Value) {}
}