From 695a6d82aec5a81c7f3050c5b62f1413722b11eb Mon Sep 17 00:00:00 2001 From: Ben Clayton Date: Wed, 27 Jul 2022 01:10:15 +0000 Subject: [PATCH] tools/cmd/gen: Add more utilities to the templates * Ensure that copyright years are preserved when regenerating. * Add 'Split' function which maps to strings.Split. * Add 'Scramble' function with messes with a string. * Add 'Import' function which allows templates to be imported, allowing for reusable macros. Change-Id: Ib77f59a989cf55addcced3e337c9031062a83470 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/97149 Commit-Queue: Dan Sinclair Reviewed-by: Dan Sinclair Kokoro: Kokoro --- tools/src/cmd/gen/main.go | 256 +++++++++++++++++++--------- tools/src/tint/intrinsic/sem/sem.go | 31 ++++ 2 files changed, 202 insertions(+), 85 deletions(-) diff --git a/tools/src/cmd/gen/main.go b/tools/src/cmd/gen/main.go index c076a73eb8..3b0c4b21c8 100644 --- a/tools/src/cmd/gen/main.go +++ b/tools/src/cmd/gen/main.go @@ -21,13 +21,18 @@ import ( "fmt" "io" "io/ioutil" + "math/rand" "os" "path/filepath" "reflect" + "regexp" + "strconv" "strings" "text/template" + "time" "unicode" + "dawn.googlesource.com/dawn/tools/src/container" "dawn.googlesource.com/dawn/tools/src/fileutils" "dawn.googlesource.com/dawn/tools/src/glob" "dawn.googlesource.com/dawn/tools/src/tint/intrinsic/gen" @@ -78,6 +83,8 @@ func run() error { return err } + cache := &genCache{} + // For each template file... for _, relTmplPath := range files { // Make tmplPath absolute @@ -89,21 +96,36 @@ func run() error { return fmt.Errorf("failed to open '%v': %w", tmplPath, err) } - // Create or update the file at relpath if the file content has changed + // Create or update the file at relpath if the file content has changed, + // preserving the copyright year in the header. // relpath is a path relative to the template writeFile := func(relpath, body string) error { + abspath := filepath.Join(filepath.Dir(tmplPath), relpath) + + copyrightYear := time.Now().Year() + + // Load the old file + existing, err := ioutil.ReadFile(abspath) + if err == nil { + // Look for the existing copyright year + if match := copyrightRegex.FindStringSubmatch(string(existing)); len(match) == 2 { + if year, err := strconv.Atoi(match[1]); err == nil { + copyrightYear = year + } + } + } + // Write the common file header sb := strings.Builder{} - sb.WriteString(fmt.Sprintf(header, filepath.ToSlash(relTmplPath))) + sb.WriteString(fmt.Sprintf(header, copyrightYear, filepath.ToSlash(relTmplPath))) sb.WriteString(body) content := sb.String() - abspath := filepath.Join(filepath.Dir(tmplPath), relpath) - return writeFileIfChanged(abspath, content) + return writeFileIfChanged(abspath, content, string(existing)) } // Write the content generated using the template and semantic info sb := strings.Builder{} - if err := generate(string(tmpl), &sb, writeFile); err != nil { + if err := generate(string(tmpl), cache, &sb, writeFile); err != nil { return fmt.Errorf("while processing '%v': %w", tmplPath, err) } @@ -119,22 +141,92 @@ func run() error { return nil } +// Cache for objects that are expensive to build, and can be reused between templates. +type genCache struct { + cached struct { + sem *sem.Sem // lazily built by sem() + intrinsicTable *gen.IntrinsicTable // lazily built by intrinsicTable() + permuter *gen.Permuter // lazily built by permute() + } +} + +// sem lazily parses and resolves the intrinsic.def file, returning the semantic info. +func (g *genCache) sem() (*sem.Sem, error) { + if g.cached.sem == nil { + // Load the builtins definition file + defPath := filepath.Join(fileutils.ProjectRoot(), defProjectRelPath) + + defSource, err := ioutil.ReadFile(defPath) + if err != nil { + return nil, err + } + + // Parse the definition file to produce an AST + ast, err := parser.Parse(string(defSource), defProjectRelPath) + if err != nil { + return nil, err + } + + // Resolve the AST to produce the semantic info + sem, err := resolver.Resolve(ast) + if err != nil { + return nil, err + } + + g.cached.sem = sem + } + return g.cached.sem, nil +} + +// intrinsicTable lazily calls and returns the result of buildIntrinsicTable(), +// caching the result for repeated calls. +func (g *genCache) intrinsicTable() (*gen.IntrinsicTable, error) { + if g.cached.intrinsicTable == nil { + sem, err := g.sem() + if err != nil { + return nil, err + } + g.cached.intrinsicTable, err = gen.BuildIntrinsicTable(sem) + if err != nil { + return nil, err + } + } + return g.cached.intrinsicTable, nil +} + +// permute lazily calls buildPermuter(), caching the result for repeated +// calls, then passes the argument to Permutator.Permute() +func (g *genCache) permute(overload *sem.Overload) ([]gen.Permutation, error) { + if g.cached.permuter == nil { + sem, err := g.sem() + if err != nil { + return nil, err + } + g.cached.permuter, err = gen.NewPermuter(sem) + if err != nil { + return nil, err + } + } + return g.cached.permuter.Permute(overload) +} + // writes content to path if the file has changed -func writeFileIfChanged(path, content string) error { - existing, err := ioutil.ReadFile(path) - if err == nil && string(existing) == content { +func writeFileIfChanged(path, newContent, oldContent string) error { + if oldContent == newContent { return nil // Not changed } if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil { return fmt.Errorf("failed to create directory for '%v': %w", path, err) } - if err := ioutil.WriteFile(path, []byte(content), 0666); err != nil { + if err := ioutil.WriteFile(path, []byte(newContent), 0666); err != nil { return fmt.Errorf("failed to write file '%v': %w", path, err) } return nil } -const header = `// Copyright 2021 The Tint Authors. +var copyrightRegex = regexp.MustCompile(`// Copyright (\d+) The`) + +const header = `// Copyright %v The Tint Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -159,12 +251,10 @@ const header = `// Copyright 2021 The Tint Authors. ` type generator struct { - t *template.Template - cached struct { - sem *sem.Sem // lazily built by sem() - intrinsicTable *gen.IntrinsicTable // lazily built by intrinsicTable() - permuter *gen.Permuter // lazily built by permute() - } + template *template.Template + cache *genCache + writeFile WriteFile + rnd *rand.Rand } // WriteFile is a function that Generate() may call to emit a new file from a @@ -176,13 +266,21 @@ type WriteFile func(relpath, content string) error // generate executes the template tmpl, writing the output to w. // See https://golang.org/pkg/text/template/ for documentation on the template // syntax. -func generate(tmpl string, w io.Writer, writeFile WriteFile) error { - g := generator{} - return g.generate(tmpl, w, writeFile) +func generate(tmpl string, cache *genCache, w io.Writer, writeFile WriteFile) error { + g := generator{ + template: template.New("