src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/edit/binding_map.go (about)

     1  package edit
     2  
     3  import (
     4  	"errors"
     5  	"sort"
     6  
     7  	"src.elv.sh/pkg/eval"
     8  	"src.elv.sh/pkg/eval/vals"
     9  	"src.elv.sh/pkg/parse"
    10  	"src.elv.sh/pkg/ui"
    11  )
    12  
    13  var errValueShouldBeFn = errors.New("value should be function")
    14  
    15  // A special Map that converts its key to ui.Key and ensures that its values
    16  // satisfy eval.CallableValue.
    17  type bindingsMap struct {
    18  	vals.Map
    19  }
    20  
    21  var emptyBindingsMap = bindingsMap{vals.EmptyMap}
    22  
    23  // Repr returns the representation of the binding table as if it were an
    24  // ordinary map keyed by strings.
    25  func (bt bindingsMap) Repr(indent int) string {
    26  	var keys ui.Keys
    27  	for it := bt.Map.Iterator(); it.HasElem(); it.Next() {
    28  		k, _ := it.Elem()
    29  		keys = append(keys, k.(ui.Key))
    30  	}
    31  	sort.Sort(keys)
    32  
    33  	builder := vals.NewMapReprBuilder(indent)
    34  
    35  	for _, k := range keys {
    36  		v, _ := bt.Map.Index(k)
    37  		builder.WritePair(parse.Quote(k.String()), indent+2, vals.Repr(v, indent+2))
    38  	}
    39  
    40  	return builder.String()
    41  }
    42  
    43  // Index converts the index to ui.Key and uses the Index of the inner Map.
    44  func (bt bindingsMap) Index(index any) (any, error) {
    45  	key, err := toKey(index)
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  	return vals.Index(bt.Map, key)
    50  }
    51  
    52  func (bt bindingsMap) HasKey(k any) bool {
    53  	_, ok := bt.Map.Index(k)
    54  	return ok
    55  }
    56  
    57  func (bt bindingsMap) GetKey(k ui.Key) eval.Callable {
    58  	v, ok := bt.Map.Index(k)
    59  	if !ok {
    60  		panic("get called when key not present")
    61  	}
    62  	return v.(eval.Callable)
    63  }
    64  
    65  // Assoc converts the index to ui.Key, ensures that the value is CallableValue,
    66  // uses the Assoc of the inner Map and converts the result to a BindingTable.
    67  func (bt bindingsMap) Assoc(k, v any) (any, error) {
    68  	key, err := toKey(k)
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  	f, ok := v.(eval.Callable)
    73  	if !ok {
    74  		return nil, errValueShouldBeFn
    75  	}
    76  	map2 := bt.Map.Assoc(key, f)
    77  	return bindingsMap{map2}, nil
    78  }
    79  
    80  // Dissoc converts the key to ui.Key and calls the Dissoc method of the inner
    81  // map.
    82  func (bt bindingsMap) Dissoc(k any) any {
    83  	key, err := toKey(k)
    84  	if err != nil {
    85  		// Key is invalid; dissoc is no-op.
    86  		return bt
    87  	}
    88  	return bindingsMap{bt.Map.Dissoc(key)}
    89  }
    90  
    91  func makeBindingMap(raw vals.Map) (bindingsMap, error) {
    92  	converted := vals.EmptyMap
    93  	for it := raw.Iterator(); it.HasElem(); it.Next() {
    94  		k, v := it.Elem()
    95  		f, ok := v.(eval.Callable)
    96  		if !ok {
    97  			return emptyBindingsMap, errValueShouldBeFn
    98  		}
    99  		key, err := toKey(k)
   100  		if err != nil {
   101  			return bindingsMap{}, err
   102  		}
   103  		converted = converted.Assoc(key, f)
   104  	}
   105  
   106  	return bindingsMap{converted}, nil
   107  }
   108  
   109  type bindingTipEntry struct {
   110  	text    string
   111  	fnNames []string
   112  }
   113  
   114  func bindingTip(text string, fnNames ...string) bindingTipEntry {
   115  	return bindingTipEntry{text, fnNames}
   116  }
   117  
   118  // Given a binding map and a list of function groups, returns a text describing
   119  // the keys that are bound to any function in each group.
   120  //
   121  // This uses Elvish qnames for both the binding map and the functions because
   122  // the place that calls bindingTips may not have direct access to them.
   123  func bindingTips(ns *eval.Ns, binding string, entries ...bindingTipEntry) ui.Text {
   124  	m := getVar(ns, binding).(bindingsMap)
   125  	var t ui.Text
   126  	for _, entry := range entries {
   127  		values := make([]any, len(entry.fnNames))
   128  		for i, fnName := range entry.fnNames {
   129  			values[i] = getVar(ns, fnName+eval.FnSuffix)
   130  		}
   131  		keys := keysBoundTo(m, values)
   132  		if len(keys) == 0 {
   133  			continue
   134  		}
   135  		if len(t) > 0 {
   136  			t = ui.Concat(t, ui.T(" "))
   137  		}
   138  		for _, k := range keys {
   139  			t = ui.Concat(t, ui.T(k.String(), ui.Inverse), ui.T(" "))
   140  		}
   141  		t = ui.Concat(t, ui.T(entry.text))
   142  	}
   143  	return t
   144  }
   145  
   146  func getVar(ns *eval.Ns, qname string) any {
   147  	segs := eval.SplitQNameSegs(qname)
   148  	for _, seg := range segs[:len(segs)-1] {
   149  		ns = ns.IndexString(seg).Get().(*eval.Ns)
   150  	}
   151  	return ns.IndexString(segs[len(segs)-1]).Get()
   152  }
   153  
   154  func keysBoundTo(m bindingsMap, values []any) []ui.Key {
   155  	var keys []ui.Key
   156  	for it := m.Iterator(); it.HasElem(); it.Next() {
   157  		k, v := it.Elem()
   158  		for _, value := range values {
   159  			if v == value {
   160  				keys = append(keys, k.(ui.Key))
   161  				continue
   162  			}
   163  		}
   164  	}
   165  	return keys
   166  }