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