• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2021 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package main
6
7import (
8	"flag"
9	"fmt"
10	"io/ioutil"
11	"path/filepath"
12	"regexp"
13	"sort"
14	"strings"
15
16	"go.skia.org/infra/go/skerr"
17	"go.skia.org/infra/go/sklog"
18)
19
20func main() {
21	inputCPPDir := flag.String("input_cpp_dir", "", "A folder containing .cpp binding files that make use of the TS_* macros")
22	outputNamespaceDir := flag.String("output_namespace_dir", "", "The folder where the ambient namespace (.d.ts) files that correspond to the C++ API should be written")
23
24	flag.Parse()
25	if *inputCPPDir == "" || *outputNamespaceDir == "" {
26		sklog.Fatalf("--input_cpp_dir and --output_namespace_dir must be specified")
27	}
28
29	cppInputs, err := ioutil.ReadDir(*inputCPPDir)
30	if err != nil {
31		sklog.Fatalf("Could not read directory %s: %s", *inputCPPDir, err)
32	}
33	for _, file := range cppInputs {
34		name := file.Name()
35		if strings.HasSuffix(name, ".cpp") {
36			fp := filepath.Join(*inputCPPDir, name)
37			contents, err := ioutil.ReadFile(fp)
38			if err != nil {
39				sklog.Fatalf("Could not read file %s: %s", fp, err)
40			}
41			namespace := strings.TrimSuffix(name, ".cpp")
42			output, err := generateAmbientNamespace(namespace, string(contents))
43			if err != nil {
44				sklog.Fatalf("Could not generate ambient namespace from %s: %s", fp, err)
45			}
46			fp = filepath.Join(*outputNamespaceDir, namespace+".d.ts")
47			if err := ioutil.WriteFile(fp, []byte(output), 0666); err != nil {
48				sklog.Fatalf("Could not write to %s: %s", fp, err)
49			}
50		}
51	}
52}
53
54var (
55	privateExportLine    = regexp.MustCompile(`TS_PRIVATE_EXPORT\("(?P<export>.+)"\)`)
56	publicExportLine     = regexp.MustCompile(`TS_EXPORT\("(?P<export>.+)"\)`)
57	classDefinitionStart = regexp.MustCompile(`class_<.+>\("(?P<name>.+)"\)`)
58	classDefinitionEnd   = regexp.MustCompile(`\.(function|constructor).+;`)
59	typeAnnotation       = regexp.MustCompile(`@type\s+(?P<optional>@optional)?\s*(?P<type>\w+)`)
60	valueObjectStart     = regexp.MustCompile(`value_object<.+>\("(?P<name>.+)"\)`)
61	valueObjectField     = regexp.MustCompile(`.field\("(?P<name>.+?)",.+\);?`)
62	constantDefinition   = regexp.MustCompile(`constant\("(?P<name>.+?)",.+\);`)
63)
64
65// generateAmbientNamespace reads through the given file contents line by line. It looks for
66// explicit macro annotations (e.g. TS_EXPORT) and embind signatures (e.g. constant("foo", "bar").
67// From these, it generates the ambient namespace declarations and returns it as a string.
68// In general, it will put declarations in alphabetical order for deterministic ordering.
69func generateAmbientNamespace(namespace, contents string) (string, error) {
70	var privateModuleFunctions []string
71	var publicModuleFunctions []string
72	var wasmObjects []*wasmObject
73	var valueObjects []*valueObject
74	var constants []string
75
76	var currentWasmObject *wasmObject
77	var currentValueObject *valueObject
78	var lastTypeAnnotation string
79	var lastTypeOptional bool
80	lines := strings.Split(contents, "\n")
81	for i, line := range lines {
82		if match := classDefinitionStart.FindStringSubmatch(line); match != nil {
83			currentWasmObject = &wasmObject{
84				name: match[1],
85			}
86			wasmObjects = append(wasmObjects, currentWasmObject)
87		}
88		if classDefinitionEnd.MatchString(line) {
89			currentWasmObject = nil
90		}
91		if match := privateExportLine.FindStringSubmatch(line); match != nil {
92			export := match[1]
93			if currentWasmObject == nil {
94				privateModuleFunctions = append(privateModuleFunctions, export)
95			} else {
96				currentWasmObject.privateMethods = append(currentWasmObject.privateMethods, export)
97			}
98		}
99		if match := publicExportLine.FindStringSubmatch(line); match != nil {
100			export := match[1]
101			if currentWasmObject == nil {
102				publicModuleFunctions = append(publicModuleFunctions, export)
103			} else {
104				if strings.HasPrefix(export, "new") {
105					currentWasmObject.constructors = append(currentWasmObject.constructors, export)
106				} else {
107					currentWasmObject.publicMethods = append(currentWasmObject.publicMethods, export)
108				}
109			}
110		}
111		if match := typeAnnotation.FindStringSubmatch(line); match != nil {
112			lastTypeOptional = match[1] != ""
113			lastTypeAnnotation = match[2]
114		}
115		if match := valueObjectStart.FindStringSubmatch(line); match != nil {
116			currentValueObject = &valueObject{
117				name: match[1],
118			}
119			valueObjects = append(valueObjects, currentValueObject)
120		}
121		if match := valueObjectField.FindStringSubmatch(line); match != nil {
122			if currentValueObject == nil {
123				// Should never happen for valid code - embind wouldn't compile with this.
124				return "", skerr.Fmt("Line %d: Found a .field outside a value object declaration", i+1)
125			}
126			if lastTypeAnnotation == "" {
127				return "", skerr.Fmt("Line %d: field %q must be preceded by a @type annotation", i+1, match[1])
128			}
129			fieldWithType := fmt.Sprintf("%s: %s", match[1], lastTypeAnnotation)
130			if lastTypeOptional {
131				fieldWithType = fmt.Sprintf("%s?: %s", match[1], lastTypeAnnotation)
132			}
133			currentValueObject.fields = append(currentValueObject.fields, fieldWithType)
134			lastTypeAnnotation = "" // It's been consumed, so reset it
135			lastTypeOptional = false
136			if strings.HasSuffix(match[0], ";") {
137				currentValueObject = nil // we've gotten to the end of our value object
138			}
139		}
140		if match := constantDefinition.FindStringSubmatch(line); match != nil {
141			if lastTypeAnnotation == "" {
142				return "", skerr.Fmt("Line %d: constant %q must be preceded by a @type annotation", i+1, match[1])
143			}
144			constWithType := fmt.Sprintf("readonly %s: %s", match[1], lastTypeAnnotation)
145			if lastTypeOptional {
146				constWithType = fmt.Sprintf("readonly %s?: %s", match[1], lastTypeAnnotation)
147			}
148			constants = append(constants, constWithType)
149			lastTypeAnnotation = "" // It's been consumed, so reset it
150			lastTypeOptional = false
151		}
152	}
153
154	sort.Strings(privateModuleFunctions)
155	sort.Strings(publicModuleFunctions)
156	sort.Slice(wasmObjects, func(i, j int) bool {
157		return wasmObjects[i].name < wasmObjects[j].name
158	})
159	sort.Slice(valueObjects, func(i, j int) bool {
160		return valueObjects[i].name < valueObjects[j].name
161	})
162	sort.Strings(constants)
163
164	output := fmt.Sprintf(`/// <reference path="embind.d.ts" />
165/* This file is autogenerated using gen_types.go and make generate */
166declare namespace %s {
167	export interface Bindings {
168`, namespace)
169
170	for _, export := range privateModuleFunctions {
171		output += fmt.Sprintf("\t\t%s\n", ensureSemicolon(export))
172	}
173	output += "\n"
174
175	for _, export := range publicModuleFunctions {
176		output += fmt.Sprintf("\t\t%s\n", ensureSemicolon(export))
177	}
178	output += "\n"
179
180	for _, obj := range wasmObjects {
181		output += fmt.Sprintf("\t\treadonly %s: %sConstructor;\n", obj.name, obj.name)
182	}
183	output += "\n"
184
185	for _, c := range constants {
186		output += fmt.Sprintf("\t\t%s;\n", c)
187	}
188
189	output += "\t}\n\n"
190
191	// The constructors for all exposed objects.
192	for _, obj := range wasmObjects {
193		sort.Strings(obj.constructors)
194		output += fmt.Sprintf("\texport interface %sConstructor {\n", obj.name)
195		for _, c := range obj.constructors {
196			output += fmt.Sprintf("\t\t%s\n", ensureSemicolon(c))
197		}
198		output += "\t}\n\n"
199	}
200	// The exposed objects themselves
201	for _, obj := range wasmObjects {
202		sort.Strings(obj.privateMethods)
203		sort.Strings(obj.publicMethods)
204		output += fmt.Sprintf("\texport interface %s extends embind.EmbindObject<%s> {\n", obj.name, obj.name)
205		for _, m := range obj.privateMethods {
206			output += fmt.Sprintf("\t\t%s\n", ensureSemicolon(m))
207		}
208		if len(obj.privateMethods) > 0 {
209			output += "\n"
210		}
211		for _, m := range obj.publicMethods {
212			output += fmt.Sprintf("\t\t%s\n", ensureSemicolon(m))
213		}
214		output += "\t}\n\n"
215	}
216
217	for _, obj := range valueObjects {
218		sort.Strings(obj.fields)
219		output += fmt.Sprintf("\texport interface %s {\n", obj.name)
220		for _, f := range obj.fields {
221			output += fmt.Sprintf("\t\t%s,\n", f)
222		}
223		output += "\t}\n\n"
224	}
225
226	output = strings.TrimSuffix(output, "\n")
227	output += "}\n"
228	return output, nil
229}
230
231func ensureSemicolon(js string) string {
232	if !strings.HasSuffix(js, ";") {
233		return js + ";"
234	}
235	return js
236}
237
238type wasmObject struct {
239	name           string
240	constructors   []string
241	publicMethods  []string
242	privateMethods []string
243}
244
245type valueObject struct {
246	name   string
247	fields []string
248}
249