1// Copyright (C) 2019 Google Inc. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// gen-grammar generates the spirv.json grammar file from the official SPIR-V 16// grammar JSON file. 17package main 18 19import ( 20 "bytes" 21 "encoding/json" 22 "flag" 23 "fmt" 24 "io/ioutil" 25 "net/http" 26 "os" 27 "path/filepath" 28 "runtime" 29 "strings" 30 "text/template" 31 32 "github.com/pkg/errors" 33 34 "github.com/KhronosGroup/SPIRV-Tools/utils/vscode/src/grammar" 35) 36 37type grammarDefinition struct { 38 name string 39 url string 40} 41 42var ( 43 spirvGrammar = grammarDefinition{ 44 name: "SPIR-V", 45 url: "https://raw.githubusercontent.com/KhronosGroup/SPIRV-Headers/master/include/spirv/unified1/spirv.core.grammar.json", 46 } 47 48 extensionGrammars = []grammarDefinition{ 49 { 50 name: "GLSL.std.450", 51 url: "https://raw.githubusercontent.com/KhronosGroup/SPIRV-Headers/master/include/spirv/unified1/extinst.glsl.std.450.grammar.json", 52 }, { 53 name: "OpenCL.std", 54 url: "https://raw.githubusercontent.com/KhronosGroup/SPIRV-Headers/master/include/spirv/unified1/extinst.opencl.std.100.grammar.json", 55 }, { 56 name: "OpenCL.DebugInfo.100", 57 url: "https://raw.githubusercontent.com/KhronosGroup/SPIRV-Headers/master/include/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json", 58 }, 59 } 60 61 templatePath = flag.String("template", "", "Path to input template file (required)") 62 outputPath = flag.String("out", "", "Path to output generated file (required)") 63 cachePath = flag.String("cache", "", "Cache directory for downloaded files (optional)") 64 65 thisDir = func() string { 66 _, file, _, _ := runtime.Caller(1) 67 return filepath.Dir(file) 68 }() 69) 70 71func main() { 72 flag.Parse() 73 if *templatePath == "" || *outputPath == "" { 74 flag.Usage() 75 os.Exit(1) 76 } 77 if err := run(); err != nil { 78 fmt.Fprintln(os.Stderr, err) 79 os.Exit(1) 80 } 81} 82 83func run() error { 84 tf, err := ioutil.ReadFile(*templatePath) 85 if err != nil { 86 return errors.Wrap(err, "Could not open template file") 87 } 88 89 type extension struct { 90 grammar.Root 91 Name string 92 } 93 94 args := struct { 95 SPIRV grammar.Root 96 Extensions []extension 97 All grammar.Root // Combination of SPIRV + Extensions 98 }{} 99 100 if args.SPIRV, err = parseGrammar(spirvGrammar); err != nil { 101 return errors.Wrap(err, "Failed to parse SPIR-V grammar file") 102 } 103 args.All.Instructions = append(args.All.Instructions, args.SPIRV.Instructions...) 104 args.All.OperandKinds = append(args.All.OperandKinds, args.SPIRV.OperandKinds...) 105 106 for _, ext := range extensionGrammars { 107 root, err := parseGrammar(ext) 108 if err != nil { 109 return errors.Wrap(err, "Failed to parse extension grammar file: "+ext.name) 110 } 111 args.Extensions = append(args.Extensions, extension{Root: root, Name: ext.name}) 112 args.All.Instructions = append(args.All.Instructions, root.Instructions...) 113 args.All.OperandKinds = append(args.All.OperandKinds, root.OperandKinds...) 114 } 115 116 t, err := template.New("tmpl"). 117 Funcs(template.FuncMap{ 118 "GenerateArguments": func() string { 119 relPath := func(path string) string { 120 rel, err := filepath.Rel(thisDir, path) 121 if err != nil { 122 return path 123 } 124 return rel 125 } 126 escape := func(str string) string { 127 return strings.ReplaceAll(str, `\`, `/`) 128 } 129 args := []string{ 130 "--template=" + escape(relPath(*templatePath)), 131 "--out=" + escape(relPath(*outputPath)), 132 } 133 return "gen-grammar.go " + strings.Join(args, " ") 134 }, 135 "OperandKindsMatch": func(k grammar.OperandKind) string { 136 sb := strings.Builder{} 137 for i, e := range k.Enumerants { 138 if i > 0 { 139 sb.WriteString("|") 140 } 141 sb.WriteString(e.Enumerant) 142 } 143 return sb.String() 144 }, 145 "AllExtOpcodes": func() string { 146 sb := strings.Builder{} 147 for _, ext := range args.Extensions { 148 for _, inst := range ext.Root.Instructions { 149 if sb.Len() > 0 { 150 sb.WriteString("|") 151 } 152 sb.WriteString(inst.Opname) 153 } 154 } 155 return sb.String() 156 }, 157 "Title": strings.Title, 158 "Replace": strings.ReplaceAll, 159 "Global": func(s string) string { 160 return strings.ReplaceAll(strings.Title(s), ".", "") 161 }, 162 }).Parse(string(tf)) 163 if err != nil { 164 return errors.Wrap(err, "Failed to parse template") 165 } 166 167 buf := bytes.Buffer{} 168 if err := t.Execute(&buf, args); err != nil { 169 return errors.Wrap(err, "Failed to execute template") 170 } 171 172 out := buf.String() 173 out = strings.ReplaceAll(out, "•", "") 174 175 if err := ioutil.WriteFile(*outputPath, []byte(out), 0777); err != nil { 176 return errors.Wrap(err, "Failed to write output file") 177 } 178 179 return nil 180} 181 182// parseGrammar downloads (or loads from the cache) the grammar file and returns 183// the parsed grammar.Root. 184func parseGrammar(def grammarDefinition) (grammar.Root, error) { 185 file, err := getOrDownload(def.name, def.url) 186 if err != nil { 187 return grammar.Root{}, errors.Wrap(err, "Failed to load grammar file") 188 } 189 190 g := grammar.Root{} 191 if err := json.NewDecoder(bytes.NewReader(file)).Decode(&g); err != nil { 192 return grammar.Root{}, errors.Wrap(err, "Failed to parse grammar file") 193 } 194 195 return g, nil 196} 197 198// getOrDownload loads the specific file from the cache, or downloads the file 199// from the given url. 200func getOrDownload(name, url string) ([]byte, error) { 201 if *cachePath != "" { 202 if err := os.MkdirAll(*cachePath, 0777); err == nil { 203 path := filepath.Join(*cachePath, name) 204 if isFile(path) { 205 return ioutil.ReadFile(path) 206 } 207 } 208 } 209 resp, err := http.Get(url) 210 if err != nil { 211 return nil, err 212 } 213 data, err := ioutil.ReadAll(resp.Body) 214 if err != nil { 215 return nil, err 216 } 217 if *cachePath != "" { 218 ioutil.WriteFile(filepath.Join(*cachePath, name), data, 0777) 219 } 220 return data, nil 221} 222 223// isFile returns true if path is a file. 224func isFile(path string) bool { 225 s, err := os.Stat(path) 226 if err != nil { 227 return false 228 } 229 return !s.IsDir() 230} 231 232// isDir returns true if path is a directory. 233func isDir(path string) bool { 234 s, err := os.Stat(path) 235 if err != nil { 236 return false 237 } 238 return s.IsDir() 239} 240