1/* 2 * gomacro - A Go interpreter with Lisp-like macros 3 * 4 * Copyright (C) 2018-2019 Massimiliano Ghilardi 5 * 6 * This Source Code Form is subject to the terms of the Mozilla Public 7 * License, v. 2.0. If a copy of the MPL was not distributed with this 8 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * 11 * template_func.go 12 * 13 * Created on Jun 06, 2018 14 * Author Massimiliano Ghilardi 15 */ 16 17package fast 18 19import ( 20 "bytes" 21 "go/ast" 22 r "reflect" 23 24 "github.com/cosmos72/gomacro/base" 25 "github.com/cosmos72/gomacro/base/output" 26 xr "github.com/cosmos72/gomacro/xreflect" 27) 28 29// an instantiated (and compiled) template function. 30type TemplateFuncInstance struct { 31 Func *func(*Env) r.Value 32 Type xr.Type 33} 34 35// a template function declaration. 36// either general, or partially specialized or fully specialized 37type TemplateFuncDecl struct { 38 Decl *ast.FuncLit // template function declaration. use a *ast.FuncLit because we will compile it with Comp.FuncLit() 39 Params []string // template param names 40 For []ast.Expr // partial or full specialization 41} 42 43// template function 44type TemplateFunc struct { 45 Master TemplateFuncDecl // master (i.e. non specialized) declaration 46 Special map[string]TemplateFuncDecl // partially or fully specialized declarations. key is TemplateFuncDecl.For converted to string 47 Instances map[I]*TemplateFuncInstance // cache of instantiated functions. key is [N]interface{}{T1, T2...} 48} 49 50func (f *TemplateFunc) String() string { 51 return f.Signature("") 52} 53 54func (f *TemplateFunc) Signature(name string) string { 55 if f == nil { 56 return "<nil>" 57 } 58 var buf bytes.Buffer // strings.Builder requires Go >= 1.10 59 buf.WriteString("template[") 60 decl := f.Master 61 for i, param := range decl.Params { 62 if i != 0 { 63 buf.WriteString(", ") 64 } 65 buf.WriteString(param) 66 } 67 buf.WriteString("] ") 68 if len(name) == 0 { 69 (*output.Stringer).Fprintf(nil, &buf, "%v", decl.Decl.Type) 70 } else { 71 (*output.Stringer).Fprintf(nil, &buf, "%v", &ast.FuncDecl{ 72 Name: &ast.Ident{Name: name}, 73 Type: decl.Decl.Type, 74 }) 75 } 76 return buf.String() 77} 78 79// DeclTemplateFunc stores a template function or method declaration 80// for later instantiation 81func (c *Comp) DeclTemplateFunc(decl *ast.FuncDecl) { 82 n := 0 83 if decl.Recv != nil { 84 n = len(decl.Recv.List) 85 } 86 if n < 2 { 87 c.Errorf("invalid template function or method declaration: expecting at least 2 receivers, found %d: %v", n, decl) 88 } 89 if decl.Recv.List[0] != nil { 90 c.Errorf("template method declaration not yet implemented: %v", decl) 91 } 92 lit, _ := decl.Recv.List[1].Type.(*ast.CompositeLit) 93 if lit == nil { 94 c.Errorf("invalid template function or method declaration: the second receiver should be an *ast.CompositeLit, found %T: %v", 95 decl.Recv.List[1].Type, decl) 96 } 97 98 params, fors := c.templateParams(lit.Elts, "function or method", decl) 99 100 fdecl := TemplateFuncDecl{ 101 Decl: &ast.FuncLit{ 102 Type: decl.Type, 103 Body: decl.Body, 104 }, 105 Params: params, 106 For: fors, 107 } 108 name := decl.Name.Name 109 110 if len(fors) == 0 { 111 // master (i.e. not specialized) declaration 112 113 if len(params) == 0 { 114 c.Errorf("cannot declare template function with zero template parameters: %v", decl.Type) 115 } 116 bind := c.NewBind(name, TemplateFuncBind, c.TypeOfPtrTemplateFunc()) 117 118 // a template function declaration has no runtime effect: 119 // it merely creates the bind for on-demand instantiation by other code 120 bind.Value = &TemplateFunc{ 121 Master: fdecl, 122 Special: make(map[string]TemplateFuncDecl), 123 Instances: make(map[I]*TemplateFuncInstance), 124 } 125 return 126 } 127 128 // partially or fully specialized declaration 129 bind := c.Binds[name] 130 if bind == nil { 131 c.Errorf("undefined identifier: %v", name) 132 } 133 fun, ok := bind.Value.(*TemplateFunc) 134 if !ok { 135 c.Errorf("symbol is not a template function, cannot declare function specializations on it: %s // %v", name, bind.Type) 136 } 137 key := c.Globals.Sprintf("%v", &ast.IndexExpr{X: decl.Name, Index: &ast.CompositeLit{Elts: fors}}) 138 if len(fun.Master.Params) != len(fors) { 139 c.Errorf("template function specialization for %d parameters, expecting %d: %s", len(fors), len(fun.Master.Params), key) 140 } 141 if _, ok := fun.Special[key]; ok { 142 c.Warnf("redefined template function specialization: %s", key) 143 } 144 fun.Special[key] = fdecl 145} 146 147// TemplateFunc compiles a template function name#[T1, T2...] instantiating it if needed. 148func (c *Comp) TemplateFunc(node *ast.IndexExpr) *Expr { 149 maker := c.templateMaker(node, TemplateFuncBind) 150 return c.templateFunc(maker, node) 151} 152 153// templateFunc compiles a template function name#[T1, T2...] instantiating it if needed. 154// node is used only for error messages 155func (c *Comp) templateFunc(maker *templateMaker, node ast.Node) *Expr { 156 if maker == nil { 157 return nil 158 } 159 fun := maker.ifun.(*TemplateFunc) 160 key := maker.ikey 161 162 instance, _ := fun.Instances[key] 163 g := &c.Globals 164 debug := g.Options&base.OptDebugTemplate != 0 165 if instance != nil { 166 if debug { 167 g.Debugf("found instantiated template function %v", maker) 168 } 169 } else { 170 if debug { 171 g.Debugf("instantiating template function %v", maker) 172 } 173 // hard part: instantiate the template function. 174 // must be instantiated in the same *Comp where it was declared! 175 instance = maker.instantiateFunc(fun, node) 176 } 177 178 var efun, retfun func(*Env) r.Value 179 eaddr := instance.Func 180 if *eaddr == nil { 181 // currently instantiating it, see comment in Comp.instantiateTemplateFunc() below. 182 // We must try again later to dereference instance.Func. 183 efun = func(env *Env) r.Value { 184 return (*eaddr)(env) 185 } 186 } else { 187 efun = *eaddr 188 } 189 upn := maker.sym.Upn 190 if debug { 191 g.Debugf("template function: %v, upn = %v, instance = %v", maker, upn, instance) 192 } 193 // switch to the correct *Env before evaluating expr 194 switch upn { 195 case 0: 196 retfun = efun 197 case 1: 198 retfun = func(env *Env) r.Value { 199 return efun(env.Outer) 200 } 201 case 2: 202 retfun = func(env *Env) r.Value { 203 return efun(env.Outer.Outer) 204 } 205 case c.Depth - 1: 206 retfun = func(env *Env) r.Value { 207 return efun(env.FileEnv) 208 } 209 case c.Depth: 210 retfun = func(env *Env) r.Value { 211 return efun(env.FileEnv.Outer) 212 } 213 default: 214 retfun = func(env *Env) r.Value { 215 for i := upn; i > 0; i-- { 216 env = env.Outer 217 } 218 return efun(env) 219 } 220 } 221 // always return a new *Expr, in case caller modifies it 222 return exprFun(instance.Type, retfun) 223} 224 225// instantiateTemplateFunc instantiates and compiles a template function. 226// node is used only for error messages 227func (maker *templateMaker) instantiateFunc(fun *TemplateFunc, node ast.Node) *TemplateFuncInstance { 228 229 // choose the specialization to use 230 _, special := maker.chooseFunc(fun) 231 232 // create a new nested Comp 233 c := NewComp(maker.comp, nil) 234 c.UpCost = 0 235 c.Depth-- 236 237 // and inject template arguments into it 238 special.injectBinds(c) 239 240 key := maker.ikey 241 panicking := true 242 defer func() { 243 if panicking { 244 delete(fun.Instances, key) 245 c.ErrorAt(node.Pos(), "error instantiating template function: %v\n\t%v", maker, recover()) 246 } 247 }() 248 249 if c.Globals.Options&base.OptDebugTemplate != 0 { 250 c.Debugf("forward-declaring template function before instantiation: %v", maker) 251 } 252 // support for template recursive functions, as for example 253 // template[T] func fib(n T) T { if n <= 2 { return 1 }; return fib#[T](n-1) + fib#[T](n-2) } 254 // requires to cache fib#[T] as instantiated **before** actually instantiating it. 255 // 256 // This is similar to the technique used for non-template recursive function, as 257 // func fib(n int) int { if n <= 2 { return 1 }; return fib(n-1) + fib(n-2) } 258 // with the difference that the cache is fun.Instances[key] instead of Comp.Binds[name] 259 260 // for such trick to work, we must: 261 // 1. compute in advance the instantiated function type 262 // 2. check TemplateFuncInstance.Func: if it's nil, take its address and dereference it later at runtime 263 t, _, _ := c.TypeFunction(special.decl.Decl.Type) 264 265 instance := &TemplateFuncInstance{Type: t, Func: new(func(*Env) r.Value)} 266 fun.Instances[key] = instance 267 268 // compile an expression that, when evaluated at runtime in the *Env 269 // where the template function was declared, returns the instantiated function 270 expr := c.FuncLit(special.decl.Decl) 271 272 *instance.Func = expr.AsX1() 273 instance.Type = expr.Type 274 275 panicking = false 276 return instance 277} 278