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