blob: a1289ef9c9287e3209e3ef8b06c43ac24bad3c12 [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"
"github.com/KhronosGroup/SPIRV-Tools/utils/vscode/src/grammar"
)
type grammarDefinition struct {
name string
url string
}
var (
spirvGrammar = grammarDefinition{
name: "SPIR-V",
url: "https://raw.githubusercontent.com/KhronosGroup/SPIRV-Headers/master/include/spirv/unified1/spirv.core.grammar.json",
}
extensionGrammars = []grammarDefinition{
{
name: "GLSL.std.450",
url: "https://raw.githubusercontent.com/KhronosGroup/SPIRV-Headers/master/include/spirv/unified1/extinst.glsl.std.450.grammar.json",
}, {
name: "OpenCL.std",
url: "https://raw.githubusercontent.com/KhronosGroup/SPIRV-Headers/master/include/spirv/unified1/extinst.opencl.std.100.grammar.json",
}, {
name: "OpenCL.DebugInfo.100",
url: "https://raw.githubusercontent.com/KhronosGroup/SPIRV-Headers/master/include/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json",
},
}
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")
}
type extension struct {
grammar.Root
Name string
}
args := struct {
SPIRV grammar.Root
Extensions []extension
All grammar.Root // Combination of SPIRV + Extensions
}{}
if args.SPIRV, err = parseGrammar(spirvGrammar); err != nil {
return errors.Wrap(err, "Failed to parse SPIR-V grammar file")
}
args.All.Instructions = append(args.All.Instructions, args.SPIRV.Instructions...)
args.All.OperandKinds = append(args.All.OperandKinds, args.SPIRV.OperandKinds...)
for _, ext := range extensionGrammars {
root, err := parseGrammar(ext)
if err != nil {
return errors.Wrap(err, "Failed to parse extension grammar file: "+ext.name)
}
args.Extensions = append(args.Extensions, extension{Root: root, Name: ext.name})
args.All.Instructions = append(args.All.Instructions, root.Instructions...)
args.All.OperandKinds = append(args.All.OperandKinds, root.OperandKinds...)
}
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
}
escape := func(str string) string {
return strings.ReplaceAll(str, `\`, `/`)
}
args := []string{
"--template=" + escape(relPath(*templatePath)),
"--out=" + escape(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()
},
"AllExtOpcodes": func() string {
sb := strings.Builder{}
for _, ext := range args.Extensions {
for _, inst := range ext.Root.Instructions {
if sb.Len() > 0 {
sb.WriteString("|")
}
sb.WriteString(inst.Opname)
}
}
return sb.String()
},
"Title": strings.Title,
"Replace": strings.ReplaceAll,
"Global": func(s string) string {
return strings.ReplaceAll(strings.Title(s), ".", "")
},
}).Parse(string(tf))
if err != nil {
return errors.Wrap(err, "Failed to parse template")
}
buf := bytes.Buffer{}
if err := t.Execute(&buf, args); 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
}
// parseGrammar downloads (or loads from the cache) the grammar file and returns
// the parsed grammar.Root.
func parseGrammar(def grammarDefinition) (grammar.Root, error) {
file, err := getOrDownload(def.name, def.url)
if err != nil {
return grammar.Root{}, errors.Wrap(err, "Failed to load grammar file")
}
g := grammar.Root{}
if err := json.NewDecoder(bytes.NewReader(file)).Decode(&g); err != nil {
return grammar.Root{}, errors.Wrap(err, "Failed to parse grammar file")
}
return g, 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()
}