package eval

import (
	
	
	

	
	
	
	
	
	
	
)

// An operation with some side effects.
type effectOp interface{ exec(*Frame) Exception }

func ( *compiler) ( *parse.Chunk) effectOp {
	return chunkOp{.Range(), .pipelineOps(.Pipelines)}
}

type chunkOp struct {
	diag.Ranging
	subops []effectOp
}

func ( chunkOp) ( *Frame) Exception {
	for ,  := range .subops {
		 := .exec()
		if  != nil {
			return 
		}
	}
	// Check for interrupts after the chunk.
	// We also check for interrupts before each pipeline, so there is no
	// need to check it before the chunk or after each pipeline.
	if .IsInterrupted() {
		return .errorp(, ErrInterrupted)
	}
	return nil
}

func ( *compiler) ( *parse.Pipeline) effectOp {
	 := .formOps(.Forms)

	return &pipelineOp{.Range(), .Background, parse.SourceText(), }
}

func ( *compiler) ( []*parse.Pipeline) []effectOp {
	 := make([]effectOp, len())
	for ,  := range  {
		[] = .pipelineOp()
	}
	return 
}

type pipelineOp struct {
	diag.Ranging
	bg     bool
	source string
	subops []effectOp
}

const pipelineChanBufferSize = 32

func ( *pipelineOp) ( *Frame) Exception {
	if .IsInterrupted() {
		return .errorp(, ErrInterrupted)
	}

	if .bg {
		 = .fork("background job" + .source)
		.intCh = nil
		.background = true
		.Evaler.addNumBgJobs(1)
	}

	 := len(.subops)

	var  sync.WaitGroup
	.Add()
	 := make([]Exception, )

	var  *Port

	// For each form, create a dedicated evalCtx and run asynchronously
	for ,  := range .subops {
		 :=  > 0
		 := .fork("[form op]")
		if  > 0 {
			.ports[0] = 
		}
		if  < -1 {
			// Each internal port pair consists of a (byte) pipe pair and a
			// channel.
			// os.Pipe sets O_CLOEXEC, which is what we want.
			, ,  := os.Pipe()
			if  != nil {
				return .errorpf(, "failed to create pipe: %s", )
			}
			 := make(chan interface{}, pipelineChanBufferSize)
			.ports[1] = &Port{
				File: , Chan: , closeFile: true, closeChan: true}
			 = &Port{
				File: , Chan: , closeFile: true, closeChan: false}
		}
		 := 
		 := &[]
		go func() {
			 := .exec()
			.Close()
			if  != nil {
				* = 
			}
			.Done()
			if  {
				// If the command has channel input, drain it. This
				// mitigates the effect of erroneous pipelines like
				// "range 100 | cat"; without draining the pipeline will
				// lock up.
				for range .InputChan() {
				}
			}
		}()
	}

	if .bg {
		// Background job, wait for form termination asynchronously.
		go func() {
			.Wait()
			.Evaler.addNumBgJobs(-1)
			 := "job " + .source + " finished"
			 := MakePipelineError()
			if  != nil {
				 += ", errors = " + .Error()
			}
			if .Evaler.getNotifyBgJobSuccess() ||  != nil {
				.ErrorFile().WriteString( + "\n")
			}
		}()
		return nil
	}
	.Wait()
	return .errorp(, MakePipelineError())
}

func ( *compiler) ( *parse.Form) effectOp {
	var  []lvalue
	var  []effectOp
	if len(.Assignments) > 0 {
		 = .assignmentOps(.Assignments)
		if .Head == nil {
			.errorpf(, `using the syntax of temporary assignment for non-temporary assignment is no longer supported; use "var" or "set" instead`)
			return nopOp{}
		}
		for ,  := range .Assignments {
			 := .parseIndexingLValue(.Left)
			 = append(, .lvalues...)
		}
		logger.Println("temporary assignment of", len(.Assignments), "pairs")
	}

	 := .redirOps(.Redirs)
	 := .formBody()

	return &formOp{.Range(), , , , }
}

func ( *compiler) ( *parse.Form) formBody {
	if .Head == nil {
		// Compiling an incomplete form node, return an empty body.
		return formBody{}
	}

	// Determine if this form is a special command.
	if ,  := cmpd.StringLiteral(.Head);  {
		,  := resolveCmdHeadInternally(, , .Head)
		if  != nil {
			 := (, )
			return formBody{specialOp: }
		}
	}

	// Determine if the form is a legacy assignment form, by looking for an
	// argument whose source is a literal "=".
	for ,  := range .Args {
		if parse.SourceText() == "=" {
			 := make([]*parse.Compound, +1)
			[0] = .Head
			copy([1:], .Args[:])
			 := .parseCompoundLValues()

			 := .compoundOps(.Args[+1:])
			var  diag.Ranging
			if len() > 0 {
				 = diag.MixedRanging([0], [len()-1])
			} else {
				 = diag.PointRanging(.Range().To)
			}
			 := seqValuesOp{, }

			return formBody{assignOp: &assignOp{.Range(), , }}
		}
	}

	var  valuesOp
	if ,  := cmpd.StringLiteral(.Head);  {
		// Head is a literal string: resolve to function or external (special
		// commands are already handled above).
		if ,  := resolveCmdHeadInternally(, , .Head);  != nil {
			 = variableOp{.Head.Range(), false,  + FnSuffix, }
		} else {
			 = literalValues(.Head, NewExternalCmd())
		}
	} else {
		// Head is not a literal string: evaluate as a normal expression.
		 = .compoundOp(.Head)
	}

	 := .compoundOps(.Args)
	 := .mapPairs(.Opts)
	return formBody{ordinaryCmd: ordinaryCmd{, , }}
}

func ( *compiler) ( []*parse.Form) []effectOp {
	 := make([]effectOp, len())
	for ,  := range  {
		[] = .formOp()
	}
	return 
}

type formOp struct {
	diag.Ranging
	tempLValues   []lvalue
	tempAssignOps []effectOp
	redirOps      []effectOp
	body          formBody
}

type formBody struct {
	// Exactly one field will be populated.
	specialOp   effectOp
	assignOp    effectOp
	ordinaryCmd ordinaryCmd
}

type ordinaryCmd struct {
	headOp valuesOp
	argOps []valuesOp
	optsOp *mapPairsOp
}

func ( *formOp) ( *Frame) ( Exception) {
	// fm here is always a sub-frame created in compiler.pipeline, so it can
	// be safely modified.

	// Temporary assignment.
	if len(.tempLValues) > 0 {
		// There is a temporary assignment.
		// Save variables.
		var  []vars.Var
		var  []interface{}
		for ,  := range .tempLValues {
			,  := derefLValue(, )
			if  != nil {
				return .errorp(, )
			}
			 = append(, )
		}
		for ,  := range  {
			// TODO(xiaq): If the variable to save is a elemVariable, save
			// the outermost variable instead.
			if  := vars.HeadOfElement();  != nil {
				 = 
				[] = 
			}
			 := .Get()
			 = append(, )
			logger.Printf("saved %s = %s", , )
		}
		// Do assignment.
		for ,  := range .tempAssignOps {
			 := .exec()
			if  != nil {
				return 
			}
		}
		// Defer variable restoration. Will be executed even if an error
		// occurs when evaling other part of the form.
		defer func() {
			for ,  := range  {
				 := []
				if  == nil {
					// TODO(xiaq): Old value is nonexistent. We should delete
					// the variable. However, since the compiler now doesn't
					// delete it, we don't delete it in the evaler either.
					 = ""
				}
				 := .Set()
				if  != nil {
					 = .errorp(, )
				}
				logger.Printf("restored %s = %s", , )
			}
		}()
	}

	// Redirections.
	for ,  := range .redirOps {
		 := .exec()
		if  != nil {
			return 
		}
	}

	if .body.specialOp != nil {
		return .body.specialOp.exec()
	}
	if .body.assignOp != nil {
		return .body.assignOp.exec()
	}

	// Ordinary command: evaluate head, arguments and options.
	 := .body.ordinaryCmd

	// Special case: evaluating an incomplete form node. Return directly.
	if .headOp == nil {
		return nil
	}

	,  := evalForCommand(, .headOp, "command")
	if  != nil {
		return .errorp(.headOp, )
	}

	var  []interface{}
	for ,  := range .argOps {
		,  := .exec()
		if  != nil {
			return 
		}
		 = append(, ...)
	}

	// TODO(xiaq): This conversion should be avoided.
	 := make(map[string]interface{})
	 := .optsOp.exec(, func(,  interface{}) Exception {
		if ,  := .(string);  {
			[] = 
			return nil
		}
		// TODO(xiaq): Point to the particular key.
		return .errorp(, errs.BadValue{
			What: "option key", Valid: "string", Actual: vals.Kind()})
	})
	if  != nil {
		return 
	}

	.traceback = .addTraceback()
	 = .Call(, , )
	if ,  := .(Exception);  {
		return 
	}
	return &exception{, .traceback}
}

func ( *Frame,  valuesOp,  string) (Callable, error) {
	,  := evalForValue(, , )
	if  != nil {
		return nil, 
	}
	switch value := .(type) {
	case Callable:
		return , nil
	case string:
		if fsutil.DontSearch() {
			return NewExternalCmd(), nil
		}
	}
	return nil, .errorp(, errs.BadValue{
		What:   ,
		Valid:  "callable or string containing slash",
		Actual: vals.Kind()})
}

func ( []interface{}) bool {
	for ,  := range  {
		if !vals.Bool() {
			return false
		}
	}
	return true
}

func ( *compiler) ( *parse.Assignment) effectOp {
	 := .parseIndexingLValue(.Left)
	 := .compoundOp(.Right)
	return &assignOp{.Range(), , }
}

func ( *compiler) ( []*parse.Assignment) []effectOp {
	 := make([]effectOp, len())
	for ,  := range  {
		[] = .assignmentOp()
	}
	return 
}

const defaultFileRedirPerm = 0644

// redir compiles a Redir into a op.
func ( *compiler) ( *parse.Redir) effectOp {
	var  valuesOp
	if .Left != nil {
		 = .compoundOp(.Left)
	}
	 := makeFlag(.Mode)
	if  == -1 {
		// TODO: Record and get redirection sign position
		.errorpf(, "bad redirection sign")
	}
	return &redirOp{.Range(), , .compoundOp(.Right), .RightIsFd, .Mode, }
}

func ( *compiler) ( []*parse.Redir) []effectOp {
	 := make([]effectOp, len())
	for ,  := range  {
		[] = .redirOp()
	}
	return 
}

func ( parse.RedirMode) int {
	switch  {
	case parse.Read:
		return os.O_RDONLY
	case parse.Write:
		return os.O_WRONLY | os.O_CREATE | os.O_TRUNC
	case parse.ReadWrite:
		return os.O_RDWR | os.O_CREATE
	case parse.Append:
		return os.O_WRONLY | os.O_CREATE | os.O_APPEND
	default:
		return -1
	}
}

type redirOp struct {
	diag.Ranging
	dstOp   valuesOp
	srcOp   valuesOp
	srcIsFd bool
	mode    parse.RedirMode
	flag    int
}

type invalidFD struct{ fd int }

func ( invalidFD) () string { return fmt.Sprintf("invalid fd: %d", .fd) }

// Returns a suitable dummy value for the channel part of the port when
// redirecting from or to a file, so that the read and write attempts fail
// silently (instead of blocking or panicking).
//
// TODO: Instead of letting read and write attempts fail silently, consider
// raising an exception instead.
func ( parse.RedirMode) chan interface{} {
	if  == parse.Read {
		// ClosedChan produces no values when reading.
		return ClosedChan
	}
	// BlackholeChan discards all values written to it.
	return BlackholeChan
}

func ( *redirOp) ( *Frame) Exception {
	var  int
	if .dstOp == nil {
		// No explicit FD destination specified; use default destinations
		switch .mode {
		case parse.Read:
			 = 0
		case parse.Write, parse.ReadWrite, parse.Append:
			 = 1
		default:
			return .errorpf(, "bad RedirMode; parser bug")
		}
	} else {
		// An explicit FD destination specified, evaluate it.
		var  error
		,  = evalForFd(, .dstOp, false, "redirection destination")
		if  != nil {
			return .errorp(, )
		}
	}

	growPorts(&.ports, +1)
	.ports[].close()

	if .srcIsFd {
		,  := evalForFd(, .srcOp, true, "redirection source")
		if  != nil {
			return .errorp(, )
		}
		switch {
		case  == -1:
			// close
			.ports[] = &Port{}
		case  >= len(.ports) || .ports[] == nil:
			return .errorp(, invalidFD{})
		default:
			.ports[] = .ports[].fork()
		}
		return nil
	}
	,  := evalForValue(, .srcOp, "redirection source")
	if  != nil {
		return .errorp(, )
	}
	switch src := .(type) {
	case string:
		,  := os.OpenFile(, .flag, defaultFileRedirPerm)
		if  != nil {
			return .errorpf(, "failed to open file %s: %s", vals.Repr(, vals.NoPretty), )
		}
		.ports[] = &Port{File: , closeFile: true, Chan: chanForFileRedir(.mode)}
	case vals.File:
		.ports[] = &Port{File: , closeFile: false, Chan: chanForFileRedir(.mode)}
	case vals.Pipe:
		var  *os.File
		switch .mode {
		case parse.Read:
			 = .ReadEnd
		case parse.Write:
			 = .WriteEnd
		default:
			return .errorpf(, "can only use < or > with pipes")
		}
		.ports[] = &Port{File: , closeFile: false, Chan: chanForFileRedir(.mode)}
	default:
		return .errorp(.srcOp, errs.BadValue{
			What:  "redirection source",
			Valid: "string, file or pipe", Actual: vals.Kind()})
	}
	return nil
}

// Makes the size of *ports at least n, adding nil's if necessary.
func ( *[]*Port,  int) {
	if len(*) >=  {
		return
	}
	 := *
	* = make([]*Port, )
	copy(*, )
}

func ( *Frame,  valuesOp,  bool,  string) (int, error) {
	,  := evalForValue(, , )
	if  != nil {
		return -1, 
	}
	switch  {
	case "stdin":
		return 0, nil
	case "stdout":
		return 1, nil
	case "stderr":
		return 2, nil
	}
	var  int
	if vals.ScanToGo(, &) == nil {
		return , nil
	} else if  == "-" &&  {
		return -1, nil
	}
	 := "fd name or number"
	if  {
		 = "fd name or number or '-'"
	}
	return -1, .errorp(, errs.BadValue{
		What: , Valid: , Actual: vals.Repr(, vals.NoPretty)})
}

type seqOp struct{ subops []effectOp }

func ( seqOp) ( *Frame) Exception {
	for ,  := range .subops {
		 := .exec()
		if  != nil {
			return 
		}
	}
	return nil
}

type nopOp struct{}

func (nopOp) ( *Frame) Exception { return nil }