package eval

// Builtin special forms. Special forms behave mostly like ordinary commands -
// they are valid commands syntactically, and can take part in pipelines - but
// they have special rules for the evaluation of their arguments and can affect
// the compilation phase (whereas ordinary commands can only affect the
// evaluation phase).
//
// For example, the "and" special form evaluates its arguments from left to
// right, and stops as soon as one booleanly false value is obtained: the
// command "and $false (fail haha)" does not produce an exception.
//
// As another example, the "del" special form removes a variable, affecting the
// compiler.
//
// Flow control structures are also implemented as special forms in elvish, with
// closures functioning as code blocks.

import (
	
	
	

	
	
	
	
	
	
)

type compileBuiltin func(*compiler, *parse.Form) effectOp

var builtinSpecials map[string]compileBuiltin

// IsBuiltinSpecial is the set of all names of builtin special forms. It is
// intended for external consumption, e.g. the syntax highlighter.
var IsBuiltinSpecial = map[string]bool{}

type noSuchModule struct{ spec string }

func ( noSuchModule) () string { return "no such module: " + .spec }

func () {
	// Needed to avoid initialization loop
	builtinSpecials = map[string]compileBuiltin{
		"var": compileVar,
		"set": compileSet,
		"del": compileDel,
		"fn":  compileFn,

		"use": compileUse,

		"and": compileAnd,
		"or":  compileOr,

		"if":    compileIf,
		"while": compileWhile,
		"for":   compileFor,
		"try":   compileTry,
	}
	for  := range builtinSpecials {
		IsBuiltinSpecial[] = true
	}
}

// VarForm = 'var' { VariablePrimary } [ '=' { Compound } ]
func ( *compiler,  *parse.Form) effectOp {
	 := lvaluesGroup{rest: -1}
	for ,  := range .Args {
		if parse.SourceText() == "=" {
			var  valuesOp
			if  == len(.Args)-1 {
				 = nopValuesOp{diag.PointRanging(.Range().To)}
			} else {
				 = seqValuesOp{
					diag.MixedRanging(.Args[+1], .Args[len(.Args)-1]),
					.compoundOps(.Args[+1:])}
			}
			return &assignOp{.Range(), , }
		}
		if len(.Indexings) != 1 {
			.errorpf(, "variable name must be a single string literal")
		}
		if len(.Indexings[0].Indicies) > 0 {
			.errorpf(, "variable name must not have indicies")
		}
		 := .Indexings[0].Head
		if !parse.ValidLHSVariable(, true) {
			.errorpf(, "invalid variable name")
		}

		 := .Value
		if !IsUnqualified() {
			.errorpf(, "variable declared in var must be unqualified")
		}
		,  := SplitSigil()
		if  == "@" {
			if .rest != -1 {
				.errorpf(, "multiple variable names with @ not allowed")
			}
			.rest = 
		}
		 := .thisScope().add()
		.lvalues = append(.lvalues,
			lvalue{.Range(), &varRef{localScope, , nil}, nil, nil})
	}
	// If there is no assignment, there is no work to be done at eval-time.
	return nopOp{}
}

// IsUnqualified returns whether name is an unqualified variable name.
func ( string) bool {
	 := strings.IndexByte(, ':')
	return  == -1 ||  == len()-1
}

// SetForm = 'set' { LHS } '=' { Compound }
func ( *compiler,  *parse.Form) effectOp {
	 := -1
	for ,  := range .Args {
		if parse.SourceText() == "=" {
			 = 
			break
		}
	}
	if  == -1 {
		.errorpf(diag.PointRanging(.Range().To), "need = and right-hand-side")
	}
	 := .parseCompoundLValues(.Args[:])
	var  valuesOp
	if  == len(.Args)-1 {
		 = nopValuesOp{diag.PointRanging(.Range().To)}
	} else {
		 = seqValuesOp{
			diag.MixedRanging(.Args[+1], .Args[len(.Args)-1]),
			.compoundOps(.Args[+1:])}
	}
	return &assignOp{.Range(), , }

}

const delArgMsg = "arguments to del must be variable or variable elements"

// DelForm = 'del' { LHS }
func ( *compiler,  *parse.Form) effectOp {
	var  []effectOp
	for ,  := range .Args {
		if len(.Indexings) != 1 {
			.errorpf(, delArgMsg)
			continue
		}
		,  := .Indexings[0].Head, .Indexings[0].Indicies
		if .Type == parse.Variable {
			.errorpf(, "arguments to del must drop $")
		} else if !parse.ValidLHSVariable(, false) {
			.errorpf(, delArgMsg)
		}

		 := .Value
		var  effectOp
		 := resolveVarRef(, , nil)
		if  == nil {
			.errorpf(, "no variable $%s", .Value)
			continue
		}
		if len() == 0 {
			if .scope == envScope {
				 = delEnvVarOp{.Range(), .subNames[0]}
			} else if .scope == localScope && len(.subNames) == 0 {
				 = delLocalVarOp{.index}
				.thisScope().deleted[.index] = true
			} else {
				.errorpf(, "only variables in local: or E: can be deleted")
				continue
			}
		} else {
			 = newDelElementOp(, .Range().From, .Range().To, .arrayOps())
		}
		 = append(, )
	}
	return seqOp{}
}

type delLocalVarOp struct{ index int }

func ( delLocalVarOp) ( *Frame) Exception {
	.local.slots[.index] = nil
	return nil
}

type delEnvVarOp struct {
	diag.Ranging
	name string
}

func ( delEnvVarOp) ( *Frame) Exception {
	return .errorp(, os.Unsetenv(.name))
}

func ( *varRef, ,  int,  []valuesOp) effectOp {
	 := make([]int, len()+1)
	[0] = 
	for ,  := range  {
		[+1] = .Range().To
	}
	return &delElemOp{, , , }
}

type delElemOp struct {
	ref      *varRef
	indexOps []valuesOp
	begin    int
	ends     []int
}

func ( *delElemOp) () diag.Ranging {
	return diag.Ranging{From: .begin, To: .ends[0]}
}

func ( *delElemOp) ( *Frame) Exception {
	var  []interface{}
	for ,  := range .indexOps {
		,  := .exec()
		if  != nil {
			return 
		}
		if len() != 1 {
			return .errorpf(, "index must evaluate to a single value in argument to del")
		}
		 = append(, [0])
	}
	 := vars.DelElement(deref(, .ref), )
	if  != nil {
		if  := vars.ElementErrorLevel();  >= 0 {
			return .errorp(diag.Ranging{From: .begin, To: .ends[]}, )
		}
		return .errorp(, )
	}
	return nil
}

// FnForm = 'fn' StringPrimary LambdaPrimary
//
// fn f []{foobar} is a shorthand for set '&'f = []{foobar}.
func ( *compiler,  *parse.Form) effectOp {
	 := .walkArgs()
	 := .next()
	 := stringLiteralOrError(, , "function name")
	 := .nextMustLambda("function body")
	.mustEnd()

	// Define the variable before compiling the body, so that the body may refer
	// to the function itself.
	 := .thisScope().add( + FnSuffix)
	 := .lambda()

	return fnOp{.Range(), , }
}

type fnOp struct {
	keywordRange diag.Ranging
	varIndex     int
	lambdaOp     valuesOp
}

func ( fnOp) ( *Frame) Exception {
	// Initialize the function variable with the builtin nop function. This step
	// allows the definition of recursive functions; the actual function will
	// never be called.
	.local.slots[.varIndex].Set(NewGoFn("<shouldn't be called>", nop))
	,  := .lambdaOp.exec()
	if  != nil {
		return 
	}
	 := [0].(*closure)
	.Op = fnWrap{.Op}
	return .errorp(.keywordRange, .local.slots[.varIndex].Set())
}

type fnWrap struct{ effectOp }

func ( fnWrap) () diag.Ranging { return .effectOp.(diag.Ranger).Range() }

func ( fnWrap) ( *Frame) Exception {
	 := .effectOp.exec()
	if  != nil && .Reason() != Return {
		// rethrow
		return 
	}
	return nil
}

// UseForm = 'use' StringPrimary
func ( *compiler,  *parse.Form) effectOp {
	var ,  string

	switch len(.Args) {
	case 0:
		 := .Head.Range().To
		.errorpf(diag.PointRanging(), "lack module name")
	case 1:
		 = stringLiteralOrError(, .Args[0], "module spec")
		// Use the last path component as the name; for instance, if path =
		// "a/b/c/d", name is "d". If path doesn't have slashes, name = path.
		 = [strings.LastIndexByte(, '/')+1:]
	case 2:
		// TODO(xiaq): Allow using variable as module path
		 = stringLiteralOrError(, .Args[0], "module spec")
		 = stringLiteralOrError(, .Args[1], "module name")
	default: // > 2
		.errorpf(diag.MixedRanging(.Args[2], .Args[len(.Args)-1]),
			"superfluous argument(s)")
	}

	return useOp{.Range(), .thisScope().add( + NsSuffix), }
}

type useOp struct {
	diag.Ranging
	varIndex int
	spec     string
}

func ( useOp) ( *Frame) Exception {
	,  := use(, .spec, )
	if  != nil {
		return .errorp(, )
	}
	.local.slots[.varIndex].Set()
	return nil
}

var bundledModules = bundled.Get()

func ( *Frame,  string,  diag.Ranger) (*Ns, error) {
	if strings.HasPrefix(, "./") || strings.HasPrefix(, "../") {
		var  string
		if .srcMeta.IsFile {
			 = filepath.Dir(.srcMeta.Name)
		} else {
			var  error
			,  = os.Getwd()
			if  != nil {
				return nil, 
			}
		}
		 := filepath.Clean( + "/" +  + ".elv")
		return useFromFile(, , , )
	}
	if ,  := .Evaler.modules[];  {
		return , nil
	}
	if ,  := bundledModules[];  {
		return evalModule(, ,
			parse.Source{Name: "[bundled " +  + "]", Code: }, )
	}
	 := .Evaler.getLibDir()
	if  == "" {
		return nil, noSuchModule{}
	}
	return useFromFile(, , +"/"++".elv", )
}

// TODO: Make access to fm.Evaler.modules concurrency-safe.
func ( *Frame, ,  string,  diag.Ranger) (*Ns, error) {
	if ,  := .Evaler.modules[];  {
		return , nil
	}
	,  := readFileUTF8()
	if  != nil {
		if os.IsNotExist() {
			return nil, noSuchModule{}
		}
		return nil, 
	}
	return evalModule(, , parse.Source{Name: , Code: , IsFile: true}, )
}

// TODO: Make access to fm.Evaler.modules concurrency-safe.
func ( *Frame,  string,  parse.Source,  diag.Ranger) (*Ns, error) {
	, ,  := .PrepareEval(, , new(Ns))
	if  != nil {
		return nil, 
	}
	// Installs the namespace before executing. This prevent circular use'es
	// from resulting in an infinite recursion.
	.Evaler.modules[] = 
	 = ()
	if  != nil {
		// Unload the namespace.
		delete(.Evaler.modules, )
		return nil, 
	}
	return , nil
}

// compileAnd compiles the "and" special form.
//
// The and special form evaluates arguments until a false-ish values is found
// and outputs it; the remaining arguments are not evaluated. If there are no
// false-ish values, the last value is output. If there are no arguments, it
// outputs $true, as if there is a hidden $true before actual arguments.
func ( *compiler,  *parse.Form) effectOp {
	return &andOrOp{.compoundOps(.Args), true, false}
}

// compileOr compiles the "or" special form.
//
// The or special form evaluates arguments until a true-ish values is found and
// outputs it; the remaining arguments are not evaluated. If there are no
// true-ish values, the last value is output. If there are no arguments, it
// outputs $false, as if there is a hidden $false before actual arguments.
func ( *compiler,  *parse.Form) effectOp {
	return &andOrOp{.compoundOps(.Args), false, true}
}

type andOrOp struct {
	argOps []valuesOp
	init   bool
	stopAt bool
}

func ( *andOrOp) ( *Frame) Exception {
	var  interface{} = vals.Bool(.init)
	for ,  := range .argOps {
		,  := .exec()
		if  != nil {
			return 
		}
		for ,  := range  {
			if vals.Bool() == .stopAt {
				.OutputChan() <- 
				return nil
			}
			 = 
		}
	}
	.OutputChan() <- 
	return nil
}

func ( *compiler,  *parse.Form) effectOp {
	 := .walkArgs()
	var  []*parse.Compound
	var  []*parse.Primary
	 := "if"
	for {
		 = append(, .next())
		 = append(, .nextMustLambda())
		if !.nextIs("elif") {
			break
		}
		 = "elif"
	}
	 := .nextMustLambdaIfAfter("else")
	.mustEnd()

	 := .compoundOps()
	 := .primaryOps()
	var  valuesOp
	if  != nil {
		 = .primaryOp()
	}

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

type ifOp struct {
	diag.Ranging
	condOps []valuesOp
	bodyOps []valuesOp
	elseOp  valuesOp
}

func ( *ifOp) ( *Frame) Exception {
	 := make([]Callable, len(.bodyOps))
	for ,  := range .bodyOps {
		[] = execLambdaOp(, )
	}
	 := execLambdaOp(, .elseOp)
	for ,  := range .condOps {
		,  := .exec(.fork("if cond"))
		if  != nil {
			return 
		}
		if allTrue() {
			return .errorp(, [].Call(.fork("if body"), NoArgs, NoOpts))
		}
	}
	if .elseOp != nil {
		return .errorp(, .Call(.fork("if else"), NoArgs, NoOpts))
	}
	return nil
}

func ( *compiler,  *parse.Form) effectOp {
	 := .walkArgs()
	 := .next()
	 := .nextMustLambda("while body")
	 := .nextMustLambdaIfAfter("else")
	.mustEnd()

	 := .compoundOp()
	 := .primaryOp()
	var  valuesOp
	if  != nil {
		 = .primaryOp()
	}

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

type whileOp struct {
	diag.Ranging
	condOp, bodyOp, elseOp valuesOp
}

func ( *whileOp) ( *Frame) Exception {
	 := execLambdaOp(, .bodyOp)
	 := execLambdaOp(, .elseOp)

	 := false
	for {
		,  := .condOp.exec(.fork("while cond"))
		if  != nil {
			return 
		}
		if !allTrue() {
			break
		}
		 = true
		 := .Call(.fork("while"), NoArgs, NoOpts)
		if  != nil {
			 := .(Exception)
			if .Reason() == Continue {
				// Do nothing
			} else if .Reason() == Break {
				break
			} else {
				return 
			}
		}
	}

	if .elseOp != nil && ! {
		return .errorp(, .Call(.fork("while else"), NoArgs, NoOpts))
	}
	return nil
}

func ( *compiler,  *parse.Form) effectOp {
	 := .walkArgs()
	 := .next()
	 := .next()
	 := .nextMustLambda("for body")
	 := .nextMustLambdaIfAfter("else")
	.mustEnd()

	 := .compileOneLValue()

	 := .compoundOp()
	 := .primaryOp()
	var  valuesOp
	if  != nil {
		 = .primaryOp()
	}

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

type forOp struct {
	diag.Ranging
	lvalue lvalue
	iterOp valuesOp
	bodyOp valuesOp
	elseOp valuesOp
}

func ( *forOp) ( *Frame) Exception {
	,  := derefLValue(, .lvalue)
	if  != nil {
		return .errorp(, )
	}
	,  := evalForValue(, .iterOp, "value being iterated")
	if  != nil {
		return .errorp(, )
	}

	 := execLambdaOp(, .bodyOp)
	 := execLambdaOp(, .elseOp)

	 := false
	var  error
	 := vals.Iterate(, func( interface{}) bool {
		 = true
		 := .Set()
		if  != nil {
			 = 
			return false
		}
		 = .Call(.fork("for"), NoArgs, NoOpts)
		if  != nil {
			 := .(Exception)
			if .Reason() == Continue {
				// do nothing
			} else if .Reason() == Break {
				return false
			} else {
				 = 
				return false
			}
		}
		return true
	})
	if  != nil {
		return .errorp(, )
	}
	if  != nil {
		return .errorp(, )
	}

	if ! &&  != nil {
		return .errorp(, .Call(.fork("for else"), NoArgs, NoOpts))
	}
	return nil
}

func ( *compiler,  *parse.Form) effectOp {
	logger.Println("compiling try")
	 := .walkArgs()
	 := .nextMustLambda("try body")
	logger.Printf("body is %q", parse.SourceText())
	var  *parse.Compound
	var  *parse.Primary
	if .nextIs("except") {
		logger.Println("except-ing")
		// Parse an optional lvalue into exceptVarNode.
		 := .peek()
		if ,  := cmpd.StringLiteral();  {
			 = 
			.next()
		}
		 = .nextMustLambda("except body")
	}
	 := .nextMustLambdaIfAfter("else")
	 := .nextMustLambdaIfAfter("finally")
	.mustEnd()

	var  lvalue
	var , , ,  valuesOp
	 = .primaryOp()
	if  != nil {
		 = .compileOneLValue()
	}
	if  != nil {
		 = .primaryOp()
	}
	if  != nil {
		 = .primaryOp()
	}
	if  != nil {
		 = .primaryOp()
	}

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

type tryOp struct {
	diag.Ranging
	bodyOp    valuesOp
	exceptVar lvalue
	exceptOp  valuesOp
	elseOp    valuesOp
	finallyOp valuesOp
}

func ( *tryOp) ( *Frame) Exception {
	 := execLambdaOp(, .bodyOp)
	var  vars.Var
	if .exceptVar.ref != nil {
		var  error
		,  = derefLValue(, .exceptVar)
		if  != nil {
			return .errorp(, )
		}
	}
	 := execLambdaOp(, .exceptOp)
	 := execLambdaOp(, .elseOp)
	 := execLambdaOp(, .finallyOp)

	 := .Call(.fork("try body"), NoArgs, NoOpts)
	if  != nil {
		if  != nil {
			if  != nil {
				 := .Set(.(Exception))
				if  != nil {
					return .errorp(.exceptVar, )
				}
			}
			 = .Call(.fork("try except"), NoArgs, NoOpts)
		}
	} else {
		if  != nil {
			 = .Call(.fork("try else"), NoArgs, NoOpts)
		}
	}
	if  != nil {
		 := .Call(.fork("try finally"), NoArgs, NoOpts)
		if  != nil {
			// TODO: If err is not nil, this discards err. Use something similar
			// to pipeline exception to expose both.
			return .errorp(, )
		}
	}
	return .errorp(, )
}

func ( *compiler) ( *parse.Compound) lvalue {
	if len(.Indexings) != 1 {
		.errorpf(, "must be valid lvalue")
	}
	 := .parseIndexingLValue(.Indexings[0])
	if .rest != -1 {
		.errorpf(.lvalues[.rest], "rest variable not allowed")
	}
	if len(.lvalues) != 1 {
		.errorpf(, "must be exactly one lvalue")
	}
	return .lvalues[0]
}

// Executes a valuesOp that is known to yield a lambda and returns the lambda.
// Returns nil if op is nil.
func ( *Frame,  valuesOp) Callable {
	if  == nil {
		return nil
	}
	,  := .exec()
	if  != nil {
		panic("must not be erroneous")
	}
	return [0].(Callable)
}