| // 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" |
| ) |
| |
| 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-Tools/master/source/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") |
| } |
| 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() |
| } |