blob: f9980b9b401f3dc9797b411e3f583eba695a4bcc [file] [log] [blame]
// Copyright (C) 2019 Google Inc.
//
// 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.
// gen-grammar generates the spirv.json grammar file from the official SPIR-V
// grammar JSON file.
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"text/template"
"github.com/pkg/errors"
"../grammar"
)
const (
spirvGrammarURL = "https://raw.githubusercontent.com/KhronosGroup/SPIRV-Headers/master/include/spirv/unified1/spirv.core.grammar.json"
spirvGrammarName = "spirv.core.grammar.json"
)
var (
templatePath = flag.String("template", "", "Path to input template file (required)")
outputPath = flag.String("out", "", "Path to output generated file (required)")
cachePath = flag.String("cache", "", "Cache directory for downloaded files (optional)")
thisDir = func() string {
_, file, _, _ := runtime.Caller(1)
return filepath.Dir(file)
}()
)
func main() {
flag.Parse()
if *templatePath == "" || *outputPath == "" {
flag.Usage()
os.Exit(1)
}
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
tf, err := ioutil.ReadFile(*templatePath)
if err != nil {
return errors.Wrap(err, "Could not open template file")
}
t, err := template.New("tmpl").
Funcs(template.FuncMap{
"GenerateArguments": func() string {
relPath := func(path string) string {
rel, err := filepath.Rel(thisDir, path)
if err != nil {
return path
}
return rel
}
args := []string{
"--template=" + relPath(*templatePath),
"--out=" + relPath(*outputPath),
}
return "gen-grammar.go " + strings.Join(args, " ")
},
"OperandKindsMatch": func(k grammar.OperandKind) string {
sb := strings.Builder{}
for i, e := range k.Enumerants {
if i > 0 {
sb.WriteString("|")
}
sb.WriteString(e.Enumerant)
}
return sb.String()
},
"Replace": strings.ReplaceAll,
}).Parse(string(tf))
if err != nil {
return errors.Wrap(err, "Failed to parse template")
}
file, err := getOrDownload(spirvGrammarName, spirvGrammarURL)
if err != nil {
return errors.Wrap(err, "Failed to load grammar file")
}
g := grammar.Root{}
if err := json.NewDecoder(bytes.NewReader(file)).Decode(&g); err != nil {
return errors.Wrap(err, "Failed to parse grammar file")
}
buf := bytes.Buffer{}
if err := t.Execute(&buf, g); err != nil {
return errors.Wrap(err, "Failed to execute template")
}
out := buf.String()
out = strings.ReplaceAll(out, "•", "")
if err := ioutil.WriteFile(*outputPath, []byte(out), 0777); err != nil {
return errors.Wrap(err, "Failed to write output file")
}
return nil
}
// getOrDownload loads the specific file from the cache, or downloads the file
// from the given url.
func getOrDownload(name, url string) ([]byte, error) {
if *cachePath != "" {
if err := os.MkdirAll(*cachePath, 0777); err == nil {
path := filepath.Join(*cachePath, name)
if isFile(path) {
return ioutil.ReadFile(path)
}
}
}
resp, err := http.Get(url)
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if *cachePath != "" {
ioutil.WriteFile(filepath.Join(*cachePath, name), data, 0777)
}
return data, nil
}
// isFile returns true if path is a file.
func isFile(path string) bool {
s, err := os.Stat(path)
if err != nil {
return false
}
return !s.IsDir()
}
// isDir returns true if path is a directory.
func isDir(path string) bool {
s, err := os.Stat(path)
if err != nil {
return false
}
return s.IsDir()
}