[tools]: Add intrinsic-gen tool Parses the intrinsics.def file, then scans the source tree for .tmpl files. Using both of these, it produces source files for use in tint. Bug: tint:832 Change-Id: I718114f4c540b9d620899e89d5758f048377ee04 Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/52642 Kokoro: Kokoro <noreply+kokoro@google.com> Commit-Queue: Ben Clayton <bclayton@google.com> Reviewed-by: David Neto <dneto@google.com>
diff --git a/tools/intrinsic-gen b/tools/intrinsic-gen new file mode 100755 index 0000000..cfbbd4e --- /dev/null +++ b/tools/intrinsic-gen
@@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# Copyright 2021 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e # Fail on any error. + +if [ ! -x "$(which go)" ] ; then + echo "error: go needs to be on \$PATH to use $0" + exit 1 +fi + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )" +ROOT_DIR="$( cd "${SCRIPT_DIR}/.." >/dev/null 2>&1 && pwd )" +BINARY="${SCRIPT_DIR}/bin/intrinsic-gen" + +# Rebuild the binary. +# Note, go caches build artifacts, so this is quick for repeat calls +pushd "${SCRIPT_DIR}/src/cmd/intrinsic-gen" > /dev/null + go build -o "${BINARY}" main.go +popd > /dev/null + +"${BINARY}" "$@"
diff --git a/tools/src/cmd/intrinsic-gen/gen/generate.go b/tools/src/cmd/intrinsic-gen/gen/generate.go new file mode 100644 index 0000000..a797840 --- /dev/null +++ b/tools/src/cmd/intrinsic-gen/gen/generate.go
@@ -0,0 +1,179 @@ +// Copyright 2021 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. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gen + +import ( + "fmt" + "io" + "reflect" + "strings" + "text/template" + "unicode" + + "dawn.googlesource.com/tint/tools/src/cmd/intrinsic-gen/sem" +) + +type generator struct { + s *sem.Sem +} + +// Generate executes the template tmpl using the provided semantic +// information, writing the output to w. +// See https://golang.org/pkg/text/template/ for documentation on the template +// syntax. +func Generate(s *sem.Sem, tmpl string, w io.Writer) error { + g := generator{s: s} + return g.generate(tmpl, w) +} + +func (g *generator) generate(tmpl string, w io.Writer) error { + t, err := template. + New("<template>"). + Funcs(map[string]interface{}{ + "Map": newMap, + "Iterate": iterate, + "Title": strings.Title, + "PascalCase": pascalCase, + "SplitDisplayName": splitDisplayName, + "IsTemplateTypeParam": is(&sem.TemplateTypeParam{}), + "IsTemplateNumberParam": is(&sem.TemplateNumberParam{}), + "IsTemplateEnumParam": is(&sem.TemplateEnumParam{}), + "IsFirstIn": isFirstIn, + "IsLastIn": isLastIn, + }). + Option("missingkey=error"). + Parse(tmpl) + if err != nil { + return err + } + return t.Execute(w, map[string]interface{}{ + "Sem": g.s, + }) +} + +// Map is a simple generic key-value map, which can be used in the template +type Map map[interface{}]interface{} + +func newMap() Map { return Map{} } + +// Put adds the key-value pair into the map. +// Put always returns an empty string so nothing is printed in the template. +func (m Map) Put(key, value interface{}) string { + m[key] = value + return "" +} + +// Get looks up and returns the value with the given key. If the map does not +// contain the given key, then nil is returned. +func (m Map) Get(key interface{}) interface{} { + return m[key] +} + +// is returns a function that returns true if the value passed to the function +// matches the type of 'ty'. +func is(ty interface{}) func(interface{}) bool { + rty := reflect.TypeOf(ty) + return func(v interface{}) bool { + return reflect.TypeOf(v) == rty + } +} + +// isFirstIn returns true if v is the first element of the given slice. +func isFirstIn(v, slice interface{}) bool { + s := reflect.ValueOf(slice) + count := s.Len() + if count == 0 { + return false + } + return s.Index(0).Interface() == v +} + +// isFirstIn returns true if v is the last element of the given slice. +func isLastIn(v, slice interface{}) bool { + s := reflect.ValueOf(slice) + count := s.Len() + if count == 0 { + return false + } + return s.Index(count-1).Interface() == v +} + +// iterate returns a slice of length 'n', with each element equal to its index. +// Useful for: {{- range Iterate $n -}}<this will be looped $n times>{{end}} +func iterate(n int) []int { + out := make([]int, n) + for i := range out { + out[i] = i + } + return out +} + +// pascalCase returns the snake-case string s transformed into 'PascalCase', +// Rules: +// * The first letter of the string is capitalized +// * Characters following an underscore or number are capitalized +// * Underscores are removed from the returned string +// See: https://en.wikipedia.org/wiki/Camel_case +func pascalCase(s string) string { + b := strings.Builder{} + upper := true + for _, r := range s { + if r == '_' { + upper = true + continue + } + if upper { + b.WriteRune(unicode.ToUpper(r)) + upper = false + } else { + b.WriteRune(r) + } + if unicode.IsNumber(r) { + upper = true + } + } + return b.String() +} + +// splitDisplayName splits displayName into parts, where text wrapped in {} +// braces are not quoted and the rest is quoted. This is used to help process +// the string value of the [[display()]] decoration. For example: +// splitDisplayName("vec{N}<{T}>") +// would return the strings: +// [`"vec"`, `N`, `"<"`, `T`, `">"`] +func splitDisplayName(displayName string) []string { + parts := []string{} + pending := strings.Builder{} + for _, r := range displayName { + switch r { + case '{': + if pending.Len() > 0 { + parts = append(parts, fmt.Sprintf(`"%v"`, pending.String())) + pending.Reset() + } + case '}': + if pending.Len() > 0 { + parts = append(parts, pending.String()) + pending.Reset() + } + default: + pending.WriteRune(r) + } + } + if pending.Len() > 0 { + parts = append(parts, fmt.Sprintf(`"%v"`, pending.String())) + } + return parts +}
diff --git a/tools/src/cmd/intrinsic-gen/main.go b/tools/src/cmd/intrinsic-gen/main.go new file mode 100644 index 0000000..51dc799 --- /dev/null +++ b/tools/src/cmd/intrinsic-gen/main.go
@@ -0,0 +1,146 @@ +// Copyright 2021 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. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// intrinsic-gen parses the <tint>/src/intrinsics.def file, then scans the +// project directory for '<file>.tmpl' files, to produce '<file>' source code +// files. +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "dawn.googlesource.com/tint/tools/src/cmd/intrinsic-gen/gen" + "dawn.googlesource.com/tint/tools/src/cmd/intrinsic-gen/parser" + "dawn.googlesource.com/tint/tools/src/cmd/intrinsic-gen/resolver" + "dawn.googlesource.com/tint/tools/src/fileutils" + "dawn.googlesource.com/tint/tools/src/glob" +) + +func main() { + if err := run(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func showUsage() { + fmt.Println(` +intrinsic-gen generates the intrinsic table for the Tint compiler + +intrinsic-gen parses the <tint>/src/intrinsics.def file, then scans the project +directory for '<file>.tmpl' files, to produce '<file>' source code files. + +usage: + intrinsic-gen + +optional flags:`) + flag.PrintDefaults() + fmt.Println(``) + os.Exit(1) +} + +func run() error { + // Load the intrinsics definition file + projectRoot := fileutils.ProjectRoot() + defPath := filepath.Join(projectRoot, "src/intrinsics.def") + + defSource, err := ioutil.ReadFile(defPath) + if err != nil { + return err + } + + // Parse the definition file to produce an AST + ast, err := parser.Parse(string(defSource), defPath) + if err != nil { + return err + } + + // Resolve the AST to produce the semantic info + sem, err := resolver.Resolve(ast) + if err != nil { + return err + } + + // Recursively find all the template files in the <tint>/src directory + srcDir := filepath.Join(projectRoot, "src") + files, err := glob.Scan(srcDir, glob.MustParseConfig(`{ + "paths": [{"include": [ "**.tmpl" ]}] + }`)) + if err != nil { + return err + } + + // For each template file... + for _, tmplPath := range files { + // Make tmplPath absolute + tmplPath := filepath.Join(srcDir, tmplPath) + + // Read the template file + tmpl, err := ioutil.ReadFile(tmplPath) + if err != nil { + return fmt.Errorf("failed to open '%v': %w", tmplPath, err) + } + + // Write the common file header + sb := strings.Builder{} + sb.WriteString(header) + + // Write the content generated using the template and semantic info + if err := gen.Generate(sem, string(tmpl), &sb); err != nil { + return fmt.Errorf("while processing '%v': %w", tmplPath, err) + } + + // Create or update the output file if the content has not changed + filePath := strings.TrimSuffix(tmplPath, ".tmpl") + if err := writeFileIfChanged(filePath, sb.String()); err != nil { + return fmt.Errorf("failed to write '%v': %w", filePath, err) + } + } + + return nil +} + +func writeFileIfChanged(path, content string) error { + existing, err := ioutil.ReadFile(path) + if err == nil && string(existing) == content { + return nil // Not changed + } + return ioutil.WriteFile(path, []byte(content), 0666) +} + +const header = `// Copyright 2021 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. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//////////////////////////////////////////////////////////////////////////////// +// File generated by tools/intrinsic-gen +// Do not modify this file directly +//////////////////////////////////////////////////////////////////////////////// + +`