[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>
This commit is contained in:
parent
126adb1bbb
commit
131cbcc6af
|
@ -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}" "$@"
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
`
|
Loading…
Reference in New Issue