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