package eval

import (
	

	
	
)

// This file implements variable resolution. Elvish has fully static lexical
// scopes, so variable resolution involves some work in the compilation phase as
// well.
//
// During compilation, a qualified variable name (whether in lvalue, like "x
// = foo", or in variable use, like "$x") is searched in compiler's staticNs
// tables to determine which scope they belong to, as well as their indicies in
// that scope. This step is just called "resolve" in the code, and it stores
// information in a varRef struct.
//
// During evaluation, the varRef is then used to look up the Var for the
// variable. This step is called "deref" in the code.
//
// The resolve phase can take place during evaluation as well for introspection.

// Keeps all the information statically about a variable referenced by a
// qualified name.
type varRef struct {
	scope    varScope
	index    int
	subNames []string
}

type varScope int

const (
	localScope varScope = 1 + iota
	captureScope
	builtinScope
	envScope
	externalScope
)

// An interface satisfied by both *compiler and *Frame. Used to implement
// resolveVarRef as a function that works for both types.
type scopeSearcher interface {
	searchLocal(k string) int
	searchCapture(k string) int
	searchBuiltin(k string, r diag.Ranger) int
}

// Resolves a qname into a varRef.
func ( scopeSearcher,  string,  diag.Ranger) *varRef {
	 = strings.TrimPrefix(, ":")
	if  := resolveVarRefLocal(, );  != nil {
		return 
	}
	if  := resolveVarRefCapture(, );  != nil {
		return 
	}
	if  := resolveVarRefBuiltin(, , );  != nil {
		return 
	}
	return nil
}

func ( scopeSearcher,  string) *varRef {
	,  := SplitQName()
	 := .searchLocal()
	if  != -1 {
		return &varRef{scope: localScope, index: , subNames: SplitQNameSegs()}
	}
	return nil
}

func ( scopeSearcher,  string) *varRef {
	,  := SplitQName()
	if  := .searchCapture();  != -1 {
		return &varRef{scope: captureScope, index: , subNames: SplitQNameSegs()}
	}
	return nil
}

func ( scopeSearcher,  string,  diag.Ranger) *varRef {
	,  := SplitQName()
	if  != "" {
		// Try special namespace first.
		switch  {
		case "local:":
			return resolveVarRefLocal(, )
		case "up:":
			return resolveVarRefCapture(, )
		case "e:":
			if strings.HasSuffix(, FnSuffix) {
				return &varRef{scope: externalScope, subNames: []string{[:len()-1]}}
			}
		case "E:":
			return &varRef{scope: envScope, subNames: []string{}}
		}
	}
	if  := .searchBuiltin(, );  != -1 {
		return &varRef{scope: builtinScope, index: , subNames: SplitQNameSegs()}
	}
	return nil
}

// Tries to resolve the command head as an internal command, i.e. a builtin
// special command or a function.
func ( scopeSearcher,  string,  diag.Ranger) (compileBuiltin, *varRef) {
	,  := builtinSpecials[]
	if  {
		return , nil
	}
	,  := SplitSigil()
	if  == "" {
		 :=  + FnSuffix
		 := resolveVarRef(, , )
		if  != nil {
			return nil, 
		}
	}
	return nil, nil
}

// Dereferences a varRef into a Var.
func ( *Frame,  *varRef) vars.Var {
	,  := derefBase(, )
	for ,  := range  {
		,  := .Get().(*Ns)
		if ! {
			return nil
		}
		 = .IndexName()
		if  == nil {
			return nil
		}
	}
	return 
}

func ( *Frame,  *varRef) (vars.Var, []string) {
	switch .scope {
	case localScope:
		return .local.slots[.index], .subNames
	case captureScope:
		return .up.slots[.index], .subNames
	case builtinScope:
		return .Evaler.Builtin().slots[.index], .subNames
	case envScope:
		return vars.FromEnv(.subNames[0]), nil
	case externalScope:
		return vars.NewReadOnly(NewExternalCmd(.subNames[0])), nil
	default:
		return nil, nil
	}
}

func ( *compiler) ( string) int {
	return .thisScope().lookup()
}

func ( *compiler) ( string) int {
	for  := len(.scopes) - 2;  >= 0; -- {
		 := .scopes[].lookup()
		if  != -1 {
			// Record the capture from i+1 to len(cp.scopes)-1, and reuse the
			// index to keep the index into the previous scope.
			 = .captures[+1].add(, true, )
			for  :=  + 2;  < len(.scopes); ++ {
				 = .captures[].add(, false, )
			}
			return 
		}
	}
	return -1
}

func ( *compiler) ( string,  diag.Ranger) int {
	 := .builtin.lookup()
	if  != -1 {
		.checkDeprecatedBuiltin(, )
	}
	return 
}

func ( *Frame) ( string) int {
	return .local.lookup()
}

func ( *Frame) ( string) int {
	return .up.lookup()
}

func ( *Frame) ( string,  diag.Ranger) int {
	return .Evaler.Builtin().lookup()
}