blob: 1e7518da25bbd1a43b1bce7033eb55b7ea3e0bb8 [file] [log] [blame]
// Copyright (C) 2016 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 main
import (
"flag"
"fmt"
"os"
"regexp"
"strconv"
"strings"
"android.googlesource.com/platform/tools/gpu/api"
"android.googlesource.com/platform/tools/gpu/api/resolver"
"android.googlesource.com/platform/tools/gpu/api/semantic"
"android.googlesource.com/platform/tools/gpu/framework/app"
"android.googlesource.com/platform/tools/gpu/framework/log"
"android.googlesource.com/platform/tools/gpu/framework/parse"
)
var (
apiPath = flag.String("api", "", "Filename of the api file to verify (required)")
cacheDir = flag.String("cache", "", "Directory for caching downloaded files (required)")
apiRoot *semantic.API
numErrors = 0
)
func main() {
app.ShortHelp = "stringgen compiles string table files to string packages and a Go definition file."
app.Run(run)
}
func run(ctx log.Context) error {
if *apiPath == "" {
app.Usage(ctx, "Mustsupply api path")
}
if *cacheDir == "" {
app.Usage(ctx, "Must supply cache dir")
}
mappings := resolver.ASTToSemantic{}
api, errs := api.Resolve(*apiPath, mappings)
if len(errs) > 0 {
for _, err := range errs {
PrintError("%v", err.Message)
}
os.Exit(2)
}
apiRoot = api
reg := DownloadRegistry()
VerifyApi(reg)
return ctx.AsError("Too many errors")
}
func PrintError(format string, a ...interface{}) {
fmt.Fprintf(os.Stderr, format, a...)
numErrors = numErrors + 1
}
func VerifyApi(reg *Registry) {
VerifyEnum(reg, false)
VerifyEnum(reg, true)
for _, cmd := range reg.Command {
VerifyCommand(reg, cmd)
}
}
func VerifyEnum(r *Registry, bitfields bool) {
name := "GLenum"
if bitfields {
name = "GLbitfield"
}
expected := make(map[string]struct{})
for _, enums := range r.Enums {
if (enums.Type == "bitmask") == bitfields && enums.Namespace == "GL" {
for _, enum := range enums.Enum {
// The following 64bit values are not proper GLenum values.
if enum.Name == "GL_TIMEOUT_IGNORED" || enum.Name == "GL_TIMEOUT_IGNORED_APPLE" {
continue
}
if enum.API == "" || enum.API == GLES1API || enum.API == GLES2API {
var value uint32
if v, err := strconv.ParseUint(enum.Value, 0, 32); err == nil {
value = uint32(v)
} else if v, err := strconv.ParseInt(enum.Value, 0, 32); err == nil {
value = uint32(v)
} else {
PrintError("Failed to parse enum value %v", enum.Value)
continue
}
expected[fmt.Sprintf("%s = 0x%08X", enum.Name, value)] = struct{}{}
}
}
}
}
seen := make(map[string]struct{})
for _, e := range apiRoot.Enums {
if e.Name() == name {
for _, m := range e.Entries {
seen[fmt.Sprintf("%s = 0x%08X", m.Name(), m.Value)] = struct{}{}
}
}
}
CompareSets(expected, seen, name+": ")
}
func CompareSets(expected, seen map[string]struct{}, msg_prefix string) {
for k := range expected {
if _, found := seen[k]; !found {
PrintError("%sMissing %s\n", msg_prefix, k)
}
}
for k := range seen {
if _, found := expected[k]; !found {
PrintError("%sUnexpected %s\n", msg_prefix, k)
}
}
return
}
var re_const_ptr_pre = regexp.MustCompile(`^const (\w+) \*$`)
var re_const_ptr_post = regexp.MustCompile(`^(.+)\bconst\*$`)
func VerifyType(cmd string, paramIndex int, expected string, seen semantic.Type) bool {
expected = strings.TrimSpace(expected)
name := seen.(semantic.NamedNode).Name()
switch s := seen.(type) {
case *semantic.Pointer:
if s.Const {
if match := re_const_ptr_pre.FindStringSubmatch(expected); match != nil {
return VerifyType(cmd, paramIndex, match[1], s.To)
}
if match := re_const_ptr_post.FindStringSubmatch(expected); match != nil {
return VerifyType(cmd, paramIndex, match[1], s.To)
}
} else {
if strings.HasSuffix(expected, "*") {
return VerifyType(cmd, paramIndex, strings.TrimSuffix(expected, "*"), s.To)
}
}
case *semantic.Pseudonym:
if s.Name() == expected {
return true
} else if expected == "GLDEBUGPROCKHR" && s.Name() == "GLDEBUGPROC" {
return true
} else {
return VerifyType(cmd, paramIndex, expected, s.To)
}
case *semantic.Enum:
if s.Name() == expected {
return true
}
case *semantic.Builtin:
if s.Name() == expected {
return true
} else if expected == "const GLchar *" && s.Name() == "string" {
return true
}
}
PrintError("%s: Param %v: Expected type %s but seen %s (%T)\n", cmd, paramIndex, expected, name, seen)
return false
}
func UniqueStrings(strs []string) (res []string) {
seen := map[string]struct{}{}
for _, str := range strs {
if _, ok := seen[str]; !ok {
res = append(res, str)
seen[str] = struct{}{}
}
}
return
}
func VerifyCommand(reg *Registry, cmd *Command) {
cmdName := cmd.Name()
versions := append(reg.GetVersions(GLES1API, cmdName), reg.GetVersions(GLES2API, cmdName)...)
extensions := append(reg.GetExtensions(GLES1API, cmdName), reg.GetExtensions(GLES2API, cmdName)...)
extensions = UniqueStrings(extensions)
if len(versions) == 0 && len(extensions) == 0 {
return // It is not a GLES command.
}
// Expected annotations.
annots := []string{}
if strings.HasPrefix(cmdName, "glDraw") && !strings.HasPrefix(cmdName, "glDrawBuffers") {
annots = append(annots, "@DrawCall")
}
for _, version := range versions {
if version == Version("1.0") && len(versions) > 1 {
continue // TODO: Add those in the api file.
}
url, _ := GetCoreManpage(version, cmdName)
annots = append(annots, fmt.Sprintf(`@Doc("%s","OpenGL ES %v")`, url, version))
}
for _, extension := range extensions {
url, _ := GetExtensionManpage(extension)
annots = append(annots, fmt.Sprintf(`@Doc("%s","%v")`, url, extension))
}
// Expected body.
apiCheck := ""
if versions != nil {
version := strings.Replace(string(versions[0]), ".", ", ", -1)
apiCheck = fmt.Sprintf("minRequiredVersion(%v)", version)
} else if extensions != nil {
overload := ""
if len(extensions) > 1 {
overload = fmt.Sprintf("%v", len(extensions))
}
apiCheck = fmt.Sprintf("requiresExtension%s(%s)", overload, strings.Join(extensions, ", "))
}
// Find existing API function.
var apiCmd *semantic.Function
for _, apiFunction := range apiRoot.Functions {
if apiFunction.Name() == cmdName {
apiCmd = apiFunction
}
}
// Print command to stdout if it is missing.
if apiCmd == nil {
params := []string{}
for _, param := range cmd.Param {
params = append(params, param.Type()+" "+param.Name)
}
fmt.Printf("%s\ncmd %s %s(%s) {\n %s\n}\n\n", strings.Join(annots, "\n"),
cmd.Proto.Type(), cmdName, strings.Join(params, ", "), apiCheck)
return
}
// Check documentation strings.
expected := make(map[string]struct{})
for _, a := range annots {
expected[a] = struct{}{}
}
seen := make(map[string]struct{})
for _, a := range apiCmd.Annotations {
if a.Name() == "Doc" || a.Name() == "DrawCall" {
seen[getSource(a.AST.Node())] = struct{}{}
}
}
CompareSets(expected, seen, fmt.Sprintf("%s: ", cmdName))
// Check parameter types.
if len(cmd.Param) != len(apiCmd.CallParameters()) {
PrintError("%s: Expected %v parameters but seen %v\n", cmdName, len(cmd.Param), len(apiCmd.CallParameters()))
} else {
for i, p := range cmd.Param {
VerifyType(cmdName, i, p.Type(), apiCmd.FullParameters[i].Type)
}
}
// Check version.
stmts := apiCmd.Block.AST.Statements
if len(stmts) == 0 {
PrintError("%s: Empty method body\n", cmdName)
} else {
expected := map[string]struct{}{apiCheck: {}}
seen := map[string]struct{}{getSource(stmts[0].Node()): {}}
CompareSets(expected, seen, fmt.Sprintf("%s: ", cmdName))
}
}
func getSource(n parse.Node) string {
return string(n.Token().Source.Runes[n.Token().Start:n.Token().End])
}