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