• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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