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