github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/eval/closure.go (about) 1 package eval 2 3 import ( 4 "fmt" 5 "sort" 6 "strconv" 7 "strings" 8 "unsafe" 9 10 "github.com/markusbkk/elvish/pkg/diag" 11 "github.com/markusbkk/elvish/pkg/eval/errs" 12 "github.com/markusbkk/elvish/pkg/eval/vals" 13 "github.com/markusbkk/elvish/pkg/eval/vars" 14 "github.com/markusbkk/elvish/pkg/parse" 15 "github.com/markusbkk/elvish/pkg/persistent/hash" 16 ) 17 18 // Closure is a function defined with Elvish code. Each Closure has its unique 19 // identity. 20 type Closure struct { 21 ArgNames []string 22 // The index of the rest argument. -1 if there is no rest argument. 23 RestArg int 24 OptNames []string 25 OptDefaults []interface{} 26 SrcMeta parse.Source 27 DefRange diag.Ranging 28 op effectOp 29 newLocal []staticVarInfo 30 captured *Ns 31 } 32 33 var _ Callable = &Closure{} 34 35 // Kind returns "fn". 36 func (*Closure) Kind() string { 37 return "fn" 38 } 39 40 // Equal compares by address. 41 func (c *Closure) Equal(rhs interface{}) bool { 42 return c == rhs 43 } 44 45 // Hash returns the hash of the address of the closure. 46 func (c *Closure) Hash() uint32 { 47 return hash.Pointer(unsafe.Pointer(c)) 48 } 49 50 // Repr returns an opaque representation "<closure 0x23333333>". 51 func (c *Closure) Repr(int) string { 52 return fmt.Sprintf("<closure %p>", c) 53 } 54 55 // Call calls a closure. 56 func (c *Closure) Call(fm *Frame, args []interface{}, opts map[string]interface{}) error { 57 // Check number of arguments. 58 if c.RestArg != -1 { 59 if len(args) < len(c.ArgNames)-1 { 60 return errs.ArityMismatch{What: "arguments", 61 ValidLow: len(c.ArgNames) - 1, ValidHigh: -1, Actual: len(args)} 62 } 63 } else { 64 if len(args) != len(c.ArgNames) { 65 return errs.ArityMismatch{What: "arguments", 66 ValidLow: len(c.ArgNames), ValidHigh: len(c.ArgNames), Actual: len(args)} 67 } 68 } 69 // Check whether all supplied options are supported. This map contains the 70 // subset of keys from opts that can be found in c.OptNames. 71 optSupported := make(map[string]struct{}) 72 for _, name := range c.OptNames { 73 _, ok := opts[name] 74 if ok { 75 optSupported[name] = struct{}{} 76 } 77 } 78 if len(optSupported) < len(opts) { 79 // Report all the options that are not supported. 80 unsupported := make([]string, 0, len(opts)-len(optSupported)) 81 for name := range opts { 82 _, supported := optSupported[name] 83 if !supported { 84 unsupported = append(unsupported, parse.Quote(name)) 85 } 86 } 87 sort.Strings(unsupported) 88 return UnsupportedOptionsError{unsupported} 89 } 90 91 // This Frame is dedicated to the current form, so we can modify it in place. 92 93 // BUG(xiaq): When evaluating closures, async access to global variables 94 // and ports can be problematic. 95 96 // Make upvalue namespace and capture variables. 97 fm.up = c.captured 98 99 // Populate local scope with arguments, options, and newly created locals. 100 localSize := len(c.ArgNames) + len(c.OptNames) + len(c.newLocal) 101 local := &Ns{make([]vars.Var, localSize), make([]staticVarInfo, localSize)} 102 103 for i, name := range c.ArgNames { 104 local.infos[i] = staticVarInfo{name, false, false} 105 } 106 if c.RestArg == -1 { 107 for i := range c.ArgNames { 108 local.slots[i] = vars.FromInit(args[i]) 109 } 110 } else { 111 for i := 0; i < c.RestArg; i++ { 112 local.slots[i] = vars.FromInit(args[i]) 113 } 114 restOff := len(args) - len(c.ArgNames) 115 local.slots[c.RestArg] = vars.FromInit( 116 vals.MakeList(args[c.RestArg : c.RestArg+restOff+1]...)) 117 for i := c.RestArg + 1; i < len(c.ArgNames); i++ { 118 local.slots[i] = vars.FromInit(args[i+restOff]) 119 } 120 } 121 122 offset := len(c.ArgNames) 123 for i, name := range c.OptNames { 124 v, ok := opts[name] 125 if !ok { 126 v = c.OptDefaults[i] 127 } 128 local.infos[offset+i] = staticVarInfo{name, false, false} 129 local.slots[offset+i] = vars.FromInit(v) 130 } 131 132 offset += len(c.OptNames) 133 for i, info := range c.newLocal { 134 local.infos[offset+i] = info 135 // TODO: Take info.readOnly into account too when creating variable 136 local.slots[offset+i] = MakeVarFromName(info.name) 137 } 138 139 fm.local = local 140 fm.srcMeta = c.SrcMeta 141 fm.defers = new([]func(*Frame) Exception) 142 exc := c.op.exec(fm) 143 excDefer := fm.runDefers() 144 // TODO: Combine exc and excDefer if both are not nil 145 if excDefer != nil && exc == nil { 146 exc = excDefer 147 } 148 return exc 149 } 150 151 var ( 152 fnDefault = NewGoFn("nop~", nop) 153 nsDefault = &Ns{} 154 ) 155 156 // MakeVarFromName creates a Var with a suitable type constraint inferred from 157 // the name. 158 func MakeVarFromName(name string) vars.Var { 159 switch { 160 case strings.HasSuffix(name, FnSuffix): 161 val := fnDefault 162 return vars.FromPtr(&val) 163 case strings.HasSuffix(name, NsSuffix): 164 val := nsDefault 165 return vars.FromPtr(&val) 166 default: 167 return vars.FromInit(nil) 168 } 169 } 170 171 // UnsupportedOptionsError is an error returned by a closure call when there are 172 // unsupported options. 173 type UnsupportedOptionsError struct { 174 Options []string 175 } 176 177 func (er UnsupportedOptionsError) Error() string { 178 if len(er.Options) == 1 { 179 return fmt.Sprintf("unsupported option: %s", er.Options[0]) 180 } 181 return fmt.Sprintf("unsupported options: %s", strings.Join(er.Options, ", ")) 182 } 183 184 func (c *Closure) Fields() vals.StructMap { return closureFields{c} } 185 186 type closureFields struct{ c *Closure } 187 188 func (closureFields) IsStructMap() {} 189 190 func (cf closureFields) ArgNames() vals.List { return vals.MakeListFromStrings(cf.c.ArgNames...) } 191 func (cf closureFields) RestArg() string { return strconv.Itoa(cf.c.RestArg) } 192 func (cf closureFields) OptNames() vals.List { return vals.MakeListFromStrings(cf.c.OptNames...) } 193 func (cf closureFields) Src() parse.Source { return cf.c.SrcMeta } 194 195 func (cf closureFields) OptDefaults() vals.List { 196 return vals.MakeList(cf.c.OptDefaults...) 197 } 198 199 func (cf closureFields) Body() string { 200 r := cf.c.op.(diag.Ranger).Range() 201 return cf.c.SrcMeta.Code[r.From:r.To] 202 } 203 204 func (cf closureFields) Def() string { 205 return cf.c.SrcMeta.Code[cf.c.DefRange.From:cf.c.DefRange.To] 206 }