1// Copyright 2021 The Dawn Authors 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// idlgen is a tool used to generate code from WebIDL files and a golang 16// template file 17package main 18 19import ( 20 "flag" 21 "fmt" 22 "io/ioutil" 23 "os" 24 "path/filepath" 25 "reflect" 26 "strings" 27 "text/template" 28 "unicode" 29 30 "github.com/ben-clayton/webidlparser/ast" 31 "github.com/ben-clayton/webidlparser/parser" 32) 33 34func main() { 35 if err := run(); err != nil { 36 fmt.Println(err) 37 os.Exit(1) 38 } 39} 40 41func showUsage() { 42 fmt.Println(` 43idlgen is a tool used to generate code from WebIDL files and a golang 44template file 45 46Usage: 47 idlgen --template=<template-path> --output=<output-path> <idl-file> [<idl-file>...]`) 48 os.Exit(1) 49} 50 51func run() error { 52 var templatePath string 53 var outputPath string 54 flag.StringVar(&templatePath, "template", "", "the template file run with the parsed WebIDL files") 55 flag.StringVar(&outputPath, "output", "", "the output file") 56 flag.Parse() 57 58 idlFiles := flag.Args() 59 60 // Check all required arguments are provided 61 if templatePath == "" || outputPath == "" || len(idlFiles) == 0 { 62 showUsage() 63 } 64 65 // Open up the output file 66 out := os.Stdout 67 if outputPath != "" { 68 file, err := os.Create(outputPath) 69 if err != nil { 70 return fmt.Errorf("failed to open output file '%v'", outputPath) 71 } 72 out = file 73 defer file.Close() 74 } 75 76 // Read the template file 77 tmpl, err := ioutil.ReadFile(templatePath) 78 if err != nil { 79 return fmt.Errorf("failed to open template file '%v'", templatePath) 80 } 81 82 // idl is the combination of the parsed idlFiles 83 idl := &ast.File{} 84 85 // Parse each of the WebIDL files and add the declarations to idl 86 for _, path := range idlFiles { 87 content, err := ioutil.ReadFile(path) 88 if err != nil { 89 return fmt.Errorf("failed to open file '%v'", path) 90 } 91 fileIDL := parser.Parse(string(content)) 92 if numErrs := len(fileIDL.Errors); numErrs != 0 { 93 errs := make([]string, numErrs) 94 for i, e := range fileIDL.Errors { 95 errs[i] = e.Message 96 } 97 return fmt.Errorf("errors found while parsing %v:\n%v", path, strings.Join(errs, "\n")) 98 } 99 idl.Declarations = append(idl.Declarations, fileIDL.Declarations...) 100 } 101 102 // Initialize the generator 103 g := generator{t: template.New(templatePath)} 104 g.workingDir = filepath.Dir(templatePath) 105 g.funcs = map[string]interface{}{ 106 // Functions exposed to the template 107 "AttributesOf": attributesOf, 108 "ConstantsOf": constantsOf, 109 "EnumEntryName": enumEntryName, 110 "Eval": g.eval, 111 "Include": g.include, 112 "IsBasicLiteral": is(ast.BasicLiteral{}), 113 "IsConstructor": isConstructor, 114 "IsDefaultDictionaryLiteral": is(ast.DefaultDictionaryLiteral{}), 115 "IsDictionary": is(ast.Dictionary{}), 116 "IsEnum": is(ast.Enum{}), 117 "IsInterface": is(ast.Interface{}), 118 "IsInterfaceOrNamespace": is(ast.Interface{}, ast.Namespace{}), 119 "IsMember": is(ast.Member{}), 120 "IsNamespace": is(ast.Namespace{}), 121 "IsNullableType": is(ast.NullableType{}), 122 "IsParametrizedType": is(ast.ParametrizedType{}), 123 "IsRecordType": is(ast.RecordType{}), 124 "IsSequenceType": is(ast.SequenceType{}), 125 "IsTypedef": is(ast.Typedef{}), 126 "IsTypeName": is(ast.TypeName{}), 127 "IsUndefinedType": isUndefinedType, 128 "IsUnionType": is(ast.UnionType{}), 129 "Lookup": g.lookup, 130 "MethodsOf": methodsOf, 131 "SetlikeOf": setlikeOf, 132 "Title": strings.Title, 133 } 134 t, err := g.t. 135 Option("missingkey=invalid"). 136 Funcs(g.funcs). 137 Parse(string(tmpl)) 138 if err != nil { 139 return fmt.Errorf("failed to parse template file '%v': %w", templatePath, err) 140 } 141 142 // simplify the definitions in the WebIDL before passing this to the template 143 idl, declarations := simplify(idl) 144 g.declarations = declarations 145 146 // Write the file header 147 fmt.Fprintf(out, header, strings.Join(os.Args[1:], "\n// ")) 148 149 // Execute the template 150 return t.Execute(out, idl) 151} 152 153// declarations is a map of WebIDL declaration name to its AST node. 154type declarations map[string]ast.Decl 155 156// nameOf returns the name of the AST node n. 157// Returns an empty string if the node is not named. 158func nameOf(n ast.Node) string { 159 switch n := n.(type) { 160 case *ast.Namespace: 161 return n.Name 162 case *ast.Interface: 163 return n.Name 164 case *ast.Dictionary: 165 return n.Name 166 case *ast.Enum: 167 return n.Name 168 case *ast.Typedef: 169 return n.Name 170 case *ast.Mixin: 171 return n.Name 172 case *ast.Includes: 173 return "" 174 default: 175 panic(fmt.Errorf("unhandled AST declaration %T", n)) 176 } 177} 178 179// simplify processes the AST 'in', returning a new AST that: 180// * Has all partial interfaces merged into a single interface. 181// * Has all mixins flattened into their place of use. 182// * Has all the declarations ordered in dependency order (leaf first) 183// simplify also returns the map of declarations in the AST. 184func simplify(in *ast.File) (*ast.File, declarations) { 185 s := simplifier{ 186 declarations: declarations{}, 187 registered: map[string]bool{}, 188 out: &ast.File{}, 189 } 190 191 // Walk the IDL declarations to merge together partial interfaces and embed 192 // mixins into their uses. 193 { 194 interfaces := map[string]*ast.Interface{} 195 mixins := map[string]*ast.Mixin{} 196 for _, d := range in.Declarations { 197 switch d := d.(type) { 198 case *ast.Interface: 199 if i, ok := interfaces[d.Name]; ok { 200 // Merge partial body into one interface 201 i.Members = append(i.Members, d.Members...) 202 } else { 203 clone := *d 204 d := &clone 205 interfaces[d.Name] = d 206 s.declarations[d.Name] = d 207 } 208 case *ast.Mixin: 209 mixins[d.Name] = d 210 s.declarations[d.Name] = d 211 case *ast.Includes: 212 // Merge mixin into interface 213 i, ok := interfaces[d.Name] 214 if !ok { 215 panic(fmt.Errorf("%v includes %v, but %v is not an interface", d.Name, d.Source, d.Name)) 216 } 217 m, ok := mixins[d.Source] 218 if !ok { 219 panic(fmt.Errorf("%v includes %v, but %v is not an mixin", d.Name, d.Source, d.Source)) 220 } 221 // Merge mixin into the interface 222 for _, member := range m.Members { 223 if member, ok := member.(*ast.Member); ok { 224 i.Members = append(i.Members, member) 225 } 226 } 227 default: 228 if name := nameOf(d); name != "" { 229 s.declarations[nameOf(d)] = d 230 } 231 } 232 } 233 } 234 235 // Now traverse the declarations in to produce the dependency-ordered 236 // output `s.out`. 237 for _, d := range in.Declarations { 238 if name := nameOf(d); name != "" { 239 s.visit(s.declarations[nameOf(d)]) 240 } 241 } 242 243 return s.out, s.declarations 244} 245 246// simplifier holds internal state for simplify() 247type simplifier struct { 248 // all AST declarations 249 declarations declarations 250 // set of visited declarations 251 registered map[string]bool 252 // the dependency-ordered output 253 out *ast.File 254} 255 256// visit traverses the AST declaration 'd' adding all dependent declarations to 257// s.out. 258func (s *simplifier) visit(d ast.Decl) { 259 register := func(name string) bool { 260 if s.registered[name] { 261 return true 262 } 263 s.registered[name] = true 264 return false 265 } 266 switch d := d.(type) { 267 case *ast.Namespace: 268 if register(d.Name) { 269 return 270 } 271 for _, m := range d.Members { 272 if m, ok := m.(*ast.Member); ok { 273 s.visitType(m.Type) 274 for _, p := range m.Parameters { 275 s.visitType(p.Type) 276 } 277 } 278 } 279 case *ast.Interface: 280 if register(d.Name) { 281 return 282 } 283 if d, ok := s.declarations[d.Inherits]; ok { 284 s.visit(d) 285 } 286 for _, m := range d.Members { 287 if m, ok := m.(*ast.Member); ok { 288 s.visitType(m.Type) 289 for _, p := range m.Parameters { 290 s.visitType(p.Type) 291 } 292 } 293 } 294 case *ast.Dictionary: 295 if register(d.Name) { 296 return 297 } 298 if d, ok := s.declarations[d.Inherits]; ok { 299 s.visit(d) 300 } 301 for _, m := range d.Members { 302 s.visitType(m.Type) 303 for _, p := range m.Parameters { 304 s.visitType(p.Type) 305 } 306 } 307 case *ast.Typedef: 308 if register(d.Name) { 309 return 310 } 311 s.visitType(d.Type) 312 case *ast.Mixin: 313 if register(d.Name) { 314 return 315 } 316 for _, m := range d.Members { 317 if m, ok := m.(*ast.Member); ok { 318 s.visitType(m.Type) 319 for _, p := range m.Parameters { 320 s.visitType(p.Type) 321 } 322 } 323 } 324 case *ast.Enum: 325 if register(d.Name) { 326 return 327 } 328 case *ast.Includes: 329 if register(d.Name) { 330 return 331 } 332 default: 333 panic(fmt.Errorf("unhandled AST declaration %T", d)) 334 } 335 336 s.out.Declarations = append(s.out.Declarations, d) 337} 338 339// visitType traverses the AST type 't' adding all dependent declarations to 340// s.out. 341func (s *simplifier) visitType(t ast.Type) { 342 switch t := t.(type) { 343 case *ast.TypeName: 344 if d, ok := s.declarations[t.Name]; ok { 345 s.visit(d) 346 } 347 case *ast.UnionType: 348 for _, t := range t.Types { 349 s.visitType(t) 350 } 351 case *ast.ParametrizedType: 352 for _, t := range t.Elems { 353 s.visitType(t) 354 } 355 case *ast.NullableType: 356 s.visitType(t.Type) 357 case *ast.SequenceType: 358 s.visitType(t.Elem) 359 case *ast.RecordType: 360 s.visitType(t.Elem) 361 default: 362 panic(fmt.Errorf("unhandled AST type %T", t)) 363 } 364} 365 366// generator holds the template generator state 367type generator struct { 368 // the root template 369 t *template.Template 370 // the working directory 371 workingDir string 372 // map of function name to function exposed to the template executor 373 funcs map[string]interface{} 374 // dependency-sorted declarations 375 declarations declarations 376} 377 378// eval executes the sub-template with the given name and arguments, returning 379// the generated output 380// args can be a single argument: 381// arg[0] 382// or a list of name-value pairs: 383// (args[0]: name, args[1]: value), (args[2]: name, args[3]: value)... 384func (g *generator) eval(template string, args ...interface{}) (string, error) { 385 target := g.t.Lookup(template) 386 if target == nil { 387 return "", fmt.Errorf("template '%v' not found", template) 388 } 389 sb := strings.Builder{} 390 var err error 391 if len(args) == 1 { 392 err = target.Execute(&sb, args[0]) 393 } else { 394 m := newMap() 395 if len(args)%2 != 0 { 396 return "", fmt.Errorf("Eval expects a single argument or list name-value pairs") 397 } 398 for i := 0; i < len(args); i += 2 { 399 name, ok := args[i].(string) 400 if !ok { 401 return "", fmt.Errorf("Eval argument %v is not a string", i) 402 } 403 m.Put(name, args[i+1]) 404 } 405 err = target.Execute(&sb, m) 406 } 407 if err != nil { 408 return "", fmt.Errorf("while evaluating '%v': %v", template, err) 409 } 410 return sb.String(), nil 411} 412 413// lookup returns the declaration with the given name, or nil if not found. 414func (g *generator) lookup(name string) ast.Decl { 415 return g.declarations[name] 416} 417 418// include loads the template with the given path, importing the declarations 419// into the scope of the current template. 420func (g *generator) include(path string) (string, error) { 421 t, err := g.t. 422 Option("missingkey=invalid"). 423 Funcs(g.funcs). 424 ParseFiles(filepath.Join(g.workingDir, path)) 425 if err != nil { 426 return "", err 427 } 428 g.t.AddParseTree(path, t.Tree) 429 return "", nil 430} 431 432// Map is a simple generic key-value map, which can be used in the template 433type Map map[interface{}]interface{} 434 435func newMap() Map { return Map{} } 436 437// Put adds the key-value pair into the map. 438// Put always returns an empty string so nothing is printed in the template. 439func (m Map) Put(key, value interface{}) string { 440 m[key] = value 441 return "" 442} 443 444// Get looks up and returns the value with the given key. If the map does not 445// contain the given key, then nil is returned. 446func (m Map) Get(key interface{}) interface{} { 447 return m[key] 448} 449 450// is returns a function that returns true if the value passed to the function 451// matches any of the types of the objects in 'prototypes'. 452func is(prototypes ...interface{}) func(interface{}) bool { 453 types := make([]reflect.Type, len(prototypes)) 454 for i, p := range prototypes { 455 types[i] = reflect.TypeOf(p) 456 } 457 return func(v interface{}) bool { 458 ty := reflect.TypeOf(v) 459 for _, rty := range types { 460 if ty == rty || ty == reflect.PtrTo(rty) { 461 return true 462 } 463 } 464 return false 465 } 466} 467 468// isConstructor returns true if the object is a constructor ast.Member. 469func isConstructor(v interface{}) bool { 470 if member, ok := v.(*ast.Member); ok { 471 if ty, ok := member.Type.(*ast.TypeName); ok { 472 return ty.Name == "constructor" 473 } 474 } 475 return false 476} 477 478// isUndefinedType returns true if the type is 'undefined' 479func isUndefinedType(ty ast.Type) bool { 480 if ty, ok := ty.(*ast.TypeName); ok { 481 return ty.Name == "undefined" 482 } 483 return false 484} 485 486// enumEntryName formats the enum entry name 's' for use in a C++ enum. 487func enumEntryName(s string) string { 488 return "k" + strings.ReplaceAll(pascalCase(strings.Trim(s, `"`)), "-", "") 489} 490 491// Method describes a WebIDL interface method 492type Method struct { 493 // Name of the method 494 Name string 495 // The list of overloads of the method 496 Overloads []*ast.Member 497} 498 499// methodsOf returns all the methods of the given WebIDL interface. 500func methodsOf(obj interface{}) []*Method { 501 iface, ok := obj.(*ast.Interface) 502 if !ok { 503 return nil 504 } 505 byName := map[string]*Method{} 506 out := []*Method{} 507 for _, member := range iface.Members { 508 member := member.(*ast.Member) 509 if !member.Const && !member.Attribute && !isConstructor(member) { 510 if method, ok := byName[member.Name]; ok { 511 method.Overloads = append(method.Overloads, member) 512 } else { 513 method = &Method{ 514 Name: member.Name, 515 Overloads: []*ast.Member{member}, 516 } 517 byName[member.Name] = method 518 out = append(out, method) 519 } 520 } 521 } 522 return out 523} 524 525// attributesOf returns all the attributes of the given WebIDL interface or 526// namespace. 527func attributesOf(obj interface{}) []*ast.Member { 528 out := []*ast.Member{} 529 add := func(m interface{}) { 530 if m := m.(*ast.Member); m.Attribute { 531 out = append(out, m) 532 } 533 } 534 switch obj := obj.(type) { 535 case *ast.Interface: 536 for _, m := range obj.Members { 537 add(m) 538 } 539 case *ast.Namespace: 540 for _, m := range obj.Members { 541 add(m) 542 } 543 default: 544 return nil 545 } 546 return out 547} 548 549// constantsOf returns all the constant values of the given WebIDL interface or 550// namespace. 551func constantsOf(obj interface{}) []*ast.Member { 552 out := []*ast.Member{} 553 add := func(m interface{}) { 554 if m := m.(*ast.Member); m.Const { 555 out = append(out, m) 556 } 557 } 558 switch obj := obj.(type) { 559 case *ast.Interface: 560 for _, m := range obj.Members { 561 add(m) 562 } 563 case *ast.Namespace: 564 for _, m := range obj.Members { 565 add(m) 566 } 567 default: 568 return nil 569 } 570 return out 571} 572 573// setlikeOf returns the setlike ast.Pattern, if obj is a setlike interface. 574func setlikeOf(obj interface{}) *ast.Pattern { 575 iface, ok := obj.(*ast.Interface) 576 if !ok { 577 return nil 578 } 579 for _, pattern := range iface.Patterns { 580 if pattern.Type == ast.Setlike { 581 return pattern 582 } 583 } 584 return nil 585} 586 587// pascalCase returns the snake-case string s transformed into 'PascalCase', 588// Rules: 589// * The first letter of the string is capitalized 590// * Characters following an underscore, hyphen or number are capitalized 591// * Underscores are removed from the returned string 592// See: https://en.wikipedia.org/wiki/Camel_case 593func pascalCase(s string) string { 594 b := strings.Builder{} 595 upper := true 596 for _, r := range s { 597 if r == '_' || r == '-' { 598 upper = true 599 continue 600 } 601 if upper { 602 b.WriteRune(unicode.ToUpper(r)) 603 upper = false 604 } else { 605 b.WriteRune(r) 606 } 607 if unicode.IsNumber(r) { 608 upper = true 609 } 610 } 611 return b.String() 612} 613 614const header = `// Copyright 2021 The Dawn Authors. 615// 616// Licensed under the Apache License, Version 2.0 (the "License"); 617// you may not use this file except in compliance with the License. 618// You may obtain a copy of the License at 619// 620// http://www.apache.org/licenses/LICENSE-2.0 621// 622// Unless required by applicable law or agreed to in writing, software 623// distributed under the License is distributed on an "AS IS" BASIS, 624// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 625// See the License for the specific language governing permissions and 626// limitations under the License. 627 628//////////////////////////////////////////////////////////////////////////////// 629// File generated by tools/cmd/idlgen.go, with the arguments: 630// %v 631// 632// Do not modify this file directly 633//////////////////////////////////////////////////////////////////////////////// 634 635` 636