aboutsummaryrefslogtreecommitdiff
path: root/api/api.go
blob: 2d1f9d6b5e07871175bec34ed3f8bf4dda72137b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// Copyright (C) 2014 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package api holds the main interface to the api language libraries.
// It provides functions for going from api files to abstract syntax trees and
// processed semantic trees.
package api

import (
	"fmt"
	"io/ioutil"
	"path/filepath"
	"sort"

	"android.googlesource.com/platform/tools/gpu/api/ast"
	"android.googlesource.com/platform/tools/gpu/api/parser"
	"android.googlesource.com/platform/tools/gpu/api/resolver"
	"android.googlesource.com/platform/tools/gpu/api/semantic"
	"android.googlesource.com/platform/tools/gpu/parse"
)

// Processor holds the state when resolving multiple api files.
type Processor struct {
	Parsed   map[string]*ast.API
	Resolved map[string]*semantic.API
}

// DefaultProcessor is the Processor used in the package level functions.
// Most applications will not need multiple instances of a Processor, and can
// just use this one.
var DefaultProcessor = Processor{
	Parsed:   map[string]*ast.API{},
	Resolved: map[string]*semantic.API{},
}

// Parse parses the api file with the DefaultProcessor.
// See Processor.Parse for details.
func Parse(apiname string) (*ast.API, parse.ErrorList) {
	return DefaultProcessor.Parse(apiname)
}

// Parse returns an ast that represents the supplied filename.
// It if the file has already been parsed, the cached ast will be returned,
// otherwise it invokes parser.Parse on the content of the supplied file name.
func (p *Processor) Parse(path string) (*ast.API, parse.ErrorList) {
	if api, ok := p.Parsed[path]; ok {
		return api, nil
	}
	info, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, parse.ErrorList{parse.Error{Message: err.Error()}}
	}
	return parser.Parse(string(info))
}

// Resolve resolves the api file with the DefaultProcessor.
// See Processor.Resolve for details.
func Resolve(apiname string, mappings resolver.ASTToSemantic) (*semantic.API, parse.ErrorList) {
	return DefaultProcessor.Resolve(apiname, mappings)
}

// Resolve returns a semantic.API that represents the supplied api file name.
// If the file has already been resolved, the cached semantic tree is returned,
// otherwise the file and all dependant files are parsed using Processor.Parse.
// Recursive calls are made to Resolve for all named imports, and then finally
// the ast and all included ast's are handed to resolver.Resolve to do semantic
// processing.
func (p *Processor) Resolve(apiname string, mappings resolver.ASTToSemantic) (*semantic.API, parse.ErrorList) {
	absname, err := filepath.Abs(apiname)
	if err != nil {
		return nil, parse.ErrorList{parse.Error{Message: err.Error()}}
	}
	wd, name := filepath.Split(absname)
	return p.resolve(wd, name, mappings)
}

func (p *Processor) resolve(wd, name string, mappings resolver.ASTToSemantic) (*semantic.API, parse.ErrorList) {
	absname := filepath.Join(wd, name)
	if api, ok := p.Resolved[absname]; ok {
		if api == nil { // reentry detected
			return nil, parse.ErrorList{parse.Error{
				Message: fmt.Sprintf("Recursive import %s", absname)},
			}
		}
		return api, nil
	}
	p.Resolved[absname] = nil // mark to prevent reentry
	// Parse all the includes
	includes := map[string]*ast.API{}
	errs := p.include(includes, wd, name)
	if len(errs) > 0 {
		return nil, errs
	}
	// Build a sorted list of includes
	names := make(sort.StringSlice, 0, len(includes))
	for name := range includes {
		names = append(names, name)
	}
	names.Sort()
	list := make([]*ast.API, len(names))
	for i, name := range names {
		list[i] = includes[name]
	}
	// Resolve all the imports
	imports := &semantic.Symbols{}
	importPaths := map[string]string{}
	for _, api := range list {
		for _, i := range api.Imports {
			if i.Name == nil {
				// unnamed imports have already been included
				continue
			}
			path := filepath.Join(wd, i.Path.Value)
			if importedPath, seen := importPaths[i.Name.Value]; seen {
				if path == importedPath {
					// import with same path and name already included
					continue
				}
				return nil, parse.ErrorList{parse.Error{
					Message: fmt.Sprintf("Import name '%s' used for different paths (%s != %s)",
						i.Name.Value, path, importedPath)},
				}
			}
			api, errs := p.resolve(wd, i.Path.Value, mappings)
			if len(errs) > 0 {
				return nil, errs
			}
			imports.Add(i.Name.Value, api)
			importPaths[i.Name.Value] = path
		}
	}
	// Now resolve the api set as a single unit
	api, errs := resolver.Resolve(list, imports, mappings)
	if len(errs) == 0 {
		p.Resolved[absname] = api
	}
	return api, errs
}

func (p *Processor) include(includes map[string]*ast.API, wd string, apiname string) parse.ErrorList {
	absname, err := filepath.Abs(filepath.Join(wd, apiname))
	if err != nil {
		return parse.ErrorList{parse.Error{Message: err.Error()}}
	}
	if _, seen := includes[absname]; seen {
		return nil
	}
	api, errs := p.Parse(absname)
	if len(errs) > 0 {
		return errs
	}
	includes[absname] = api
	for _, i := range api.Imports {
		if i.Name != nil {
			// named imports don't get merged
			continue
		}
		errs := p.include(includes, wd, i.Path.Value)
		if len(errs) > 0 {
			return errs
		}
	}
	return nil
}