Source file src/simd/archsimd/_gen/specgen/template.go

     1  // Copyright 2026 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package specgen
     6  
     7  import (
     8  	"bytes"
     9  	"cmp"
    10  	"fmt"
    11  	"reflect"
    12  	"regexp"
    13  	"simd/archsimd/_gen/specgen/specexpr"
    14  	"strings"
    15  	"text/template"
    16  )
    17  
    18  // specTemplate wraps a parsed text/template.Template, or a plain string if no
    19  // template actions were present.
    20  type specTemplate struct {
    21  	raw  string
    22  	tmpl *template.Template
    23  }
    24  
    25  var templateFuncs = template.FuncMap{
    26  	"title": func(v any) string {
    27  		s := fmt.Sprint(v)
    28  		if len(s) == 0 {
    29  			return ""
    30  		}
    31  		return strings.ToTitle(s[:1]) + s[1:]
    32  	},
    33  	"lt": func(a, b any) (bool, error) {
    34  		res, ok := compare(a, b)
    35  		if !ok {
    36  			return false, fmt.Errorf("incomparable types: %T and %T", a, b)
    37  		}
    38  		return res < 0, nil
    39  	},
    40  	"le": func(a, b any) (bool, error) {
    41  		res, ok := compare(a, b)
    42  		if !ok {
    43  			return false, fmt.Errorf("incomparable types: %T and %T", a, b)
    44  		}
    45  		return res <= 0, nil
    46  	},
    47  	"gt": func(a, b any) (bool, error) {
    48  		res, ok := compare(a, b)
    49  		if !ok {
    50  			return false, fmt.Errorf("incomparable types: %T and %T", a, b)
    51  		}
    52  		return res > 0, nil
    53  	},
    54  	"ge": func(a, b any) (bool, error) {
    55  		res, ok := compare(a, b)
    56  		if !ok {
    57  			return false, fmt.Errorf("incomparable types: %T and %T", a, b)
    58  		}
    59  		return res >= 0, nil
    60  	},
    61  	"eq": func(a, b any) bool {
    62  		if res, ok := compare(a, b); ok {
    63  			return res == 0
    64  		}
    65  		return reflect.DeepEqual(a, b)
    66  	},
    67  	"ne": func(a, b any) bool {
    68  		if res, ok := compare(a, b); ok {
    69  			return res != 0
    70  		}
    71  		return !reflect.DeepEqual(a, b)
    72  	},
    73  }
    74  
    75  func toSpecNum(v any) (specexpr.Num, bool) {
    76  	switch v := v.(type) {
    77  	case specexpr.Num:
    78  		return v, true
    79  	case int:
    80  		return specexpr.Int(v), true
    81  	case int64:
    82  		return specexpr.Int(v), true
    83  	default:
    84  		return nil, false
    85  	}
    86  }
    87  
    88  func compare(a, b any) (int, bool) {
    89  	if na, okA := toSpecNum(a); okA {
    90  		if nb, okB := toSpecNum(b); okB {
    91  			return na.Compare(nb)
    92  		}
    93  	}
    94  	if sa, okA := a.(string); okA {
    95  		if sb, okB := b.(string); okB {
    96  			return cmp.Compare(sa, sb), true
    97  		}
    98  	}
    99  	return 0, false
   100  }
   101  
   102  // newSpecTemplate parses a spec template string. If tmpl does not contain "{{",
   103  // it is treated as a raw string literal without template overhead.
   104  func newSpecTemplate(tmpl string) (specTemplate, error) {
   105  	if !strings.Contains(tmpl, "{{") {
   106  		return specTemplate{raw: tmpl, tmpl: nil}, nil
   107  	}
   108  
   109  	t, err := template.New("").Option("missingkey=error").Funcs(templateFuncs).Parse(tmpl)
   110  	if err != nil {
   111  		return specTemplate{}, err
   112  	}
   113  	return specTemplate{raw: tmpl, tmpl: t}, nil
   114  }
   115  
   116  func (s *specTemplate) expand(data any) (string, error) {
   117  	if s.tmpl == nil {
   118  		return s.raw, nil
   119  	}
   120  	var buf bytes.Buffer
   121  	if err := s.tmpl.Execute(&buf, data); err != nil {
   122  		return "", err
   123  	}
   124  	return buf.String(), nil
   125  }
   126  
   127  var reMultipleNewlines = regexp.MustCompile(`\n{3,}`)
   128  
   129  func cleanDocNewlines(s string) string {
   130  	s = reMultipleNewlines.ReplaceAllString(s, "\n\n")
   131  	trimmed := strings.TrimRight(s, " \t\n")
   132  	if trimmed == "" {
   133  		return ""
   134  	}
   135  	return trimmed + "\n"
   136  }
   137  
   138  // expandNameAndDoc instantiates the API function name and doc comment for sFn
   139  // using the solved variable bindings in b.
   140  func (sFn *specFunc) expandNameAndDoc(ctx context, b *specexpr.Bindings) (name, doc string) {
   141  	data := make(map[string]any)
   142  	for v, val := range b.All() {
   143  		data[string(v)] = val
   144  		if s, ok := strings.CutPrefix(string(v), "$"); ok {
   145  			data[s] = val
   146  		}
   147  	}
   148  
   149  	var err error
   150  	name, err = sFn.NameTmpl.expand(data)
   151  	if err != nil {
   152  		ctx.errorf("expanding function name template: %s", err)
   153  		name = sFn.Name
   154  	}
   155  
   156  	data["Name"] = name
   157  	doc, err = sFn.Doc.expand(data)
   158  	if err != nil {
   159  		ctx.errorf("expanding doc template: %s", err)
   160  		doc = sFn.Doc.raw
   161  	}
   162  
   163  	doc = cleanDocNewlines(doc)
   164  
   165  	if name != sFn.Name {
   166  		doc = regexp.MustCompile(`\b`+regexp.QuoteMeta(sFn.Name)+`\b`).ReplaceAllLiteralString(doc, name)
   167  	}
   168  
   169  	return name, doc
   170  }
   171  

View as plain text