228 lines
7.0 KiB
Go
228 lines
7.0 KiB
Go
/*
|
|
* Copyright (C) 2022 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 (
|
|
"bufio"
|
|
"log"
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
func Parse(sourcePath string) []Scope {
|
|
sourceFile, err := os.Open(sourcePath)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer sourceFile.Close()
|
|
context := parserContext{}
|
|
lineScanner := bufio.NewScanner(sourceFile)
|
|
for lineNumber := 1; lineScanner.Scan(); lineNumber++ {
|
|
context.scanCppCodeline(lineScanner.Text(), lineNumber)
|
|
}
|
|
if err := lineScanner.Err(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
return context.definitions
|
|
}
|
|
|
|
type Scope interface {
|
|
BaseName() string
|
|
QualifiedName() string
|
|
}
|
|
|
|
type StructField struct {
|
|
Type string
|
|
Name string
|
|
DefaultValue string
|
|
SkipJson bool
|
|
SkipJavaScript bool
|
|
}
|
|
|
|
type StructDefinition struct {
|
|
StructName string
|
|
Qualifier string
|
|
Fields []StructField
|
|
}
|
|
|
|
type EnumDefinition struct {
|
|
EnumName string
|
|
Qualifier string
|
|
Values []string
|
|
}
|
|
|
|
type generalScope struct{}
|
|
|
|
func (defn StructDefinition) BaseName() string { return defn.StructName }
|
|
func (defn StructDefinition) QualifiedName() string { return defn.Qualifier + defn.StructName }
|
|
|
|
func (defn EnumDefinition) BaseName() string { return defn.EnumName }
|
|
func (defn EnumDefinition) QualifiedName() string { return defn.Qualifier + defn.EnumName }
|
|
|
|
func (defn generalScope) BaseName() string { return "" }
|
|
func (defn generalScope) QualifiedName() string { return "" }
|
|
|
|
type parserContext struct {
|
|
stack []Scope
|
|
definitions []Scope
|
|
insideComment bool
|
|
cppTokenizer *regexp.Regexp
|
|
assignmentMatcher *regexp.Regexp
|
|
floatMatcher *regexp.Regexp
|
|
vecMatcher *regexp.Regexp
|
|
}
|
|
|
|
func (context parserContext) generateQualifier() string {
|
|
qualifier := ""
|
|
for _, defn := range context.stack {
|
|
if defn.BaseName() != "" {
|
|
qualifier = qualifier + defn.BaseName() + "::"
|
|
}
|
|
}
|
|
return qualifier
|
|
}
|
|
|
|
// Validate and transform the RHS of an assignment.
|
|
// For vectors, this converts curly braces into square brackets.
|
|
func (context parserContext) distillValue(cppvalue string, lineNumber int) string {
|
|
cppvalue = strings.TrimSpace(cppvalue)
|
|
|
|
// Remove trailing "f" from floats, which isn't allowed in JavaScript.
|
|
if context.floatMatcher.MatchString(cppvalue) {
|
|
cppvalue = context.floatMatcher.ReplaceAllString(cppvalue, "$1")
|
|
}
|
|
|
|
// There are many ways to declare vector values (multi-arg constructor, single-arg
|
|
// constructor, curly braces with type, curly braces without type), so just poop out if
|
|
// the syntax is anything other than "curly braces without type".
|
|
if strings.Contains(cppvalue, "math::") || strings.Contains(cppvalue, "Color") {
|
|
log.Fatalf("%d: vectors must have the form {x, y ...}", lineNumber)
|
|
}
|
|
|
|
// Assume it's a vector if there's a curly brace.
|
|
if strings.Contains(cppvalue, "{") {
|
|
if context.vecMatcher.MatchString(cppvalue) {
|
|
cppvalue = context.vecMatcher.ReplaceAllString(cppvalue, "[$1]")
|
|
} else {
|
|
log.Fatalf("%d: vectors must have the form {x, y ...}", lineNumber)
|
|
}
|
|
}
|
|
|
|
if len(cppvalue) == 0 {
|
|
log.Fatalf("%d: empty value specified", lineNumber)
|
|
}
|
|
|
|
return cppvalue
|
|
}
|
|
|
|
func (context *parserContext) scanCppCodeline(codeline string, lineNumber int) {
|
|
if context.cppTokenizer == nil {
|
|
// https://github.com/google/re2/wiki/Syntax
|
|
context.cppTokenizer = regexp.MustCompile(`((?:/\*)|(?:\*/)|(?:;)|(?://)|(?:\})|(?:\{))`)
|
|
context.assignmentMatcher = regexp.MustCompile(`=(.*);`)
|
|
context.floatMatcher = regexp.MustCompile(`(\-?[0-9]+\.[0-9]*)f?`)
|
|
context.vecMatcher = regexp.MustCompile(`\{(\s*\-?[0-9\.]+\s*(,\s*\-?[0-9\.]+\s*){1,})\}`)
|
|
}
|
|
codeline = context.cppTokenizer.ReplaceAllString(codeline, " $1 ")
|
|
scanner := bufio.NewScanner(strings.NewReader(codeline))
|
|
scanner.Split(bufio.ScanWords)
|
|
inPlaceDefinition := ""
|
|
for scanner.Scan() {
|
|
token := scanner.Text()
|
|
switch {
|
|
case token == "//":
|
|
return
|
|
case token == "/*":
|
|
context.insideComment = true
|
|
case token == "*/":
|
|
context.insideComment = false
|
|
case context.insideComment:
|
|
// Do nothing inside a comment.
|
|
case token == ";":
|
|
// Do nothing.
|
|
case token == "{":
|
|
context.stack = append(context.stack, &generalScope{})
|
|
case token == "}":
|
|
depth := len(context.stack) - 1
|
|
if depth < 0 {
|
|
log.Fatalf("%d: bizarre nesting", lineNumber)
|
|
}
|
|
switch defn := context.stack[depth].(type) {
|
|
case *generalScope:
|
|
// Do nothing here since this scope is neither a struct definition
|
|
// nor an enum definition. (e.g. it could be a namespace or initializer)
|
|
default:
|
|
inPlaceDefinition = defn.BaseName()
|
|
context.definitions = append(context.definitions, defn)
|
|
}
|
|
context.stack = context.stack[:depth]
|
|
case token == "struct":
|
|
if !scanner.Scan() {
|
|
log.Fatalf("%d: bizarre struct", lineNumber)
|
|
}
|
|
if !strings.Contains(codeline, "{") || strings.Contains(codeline, "}") {
|
|
log.Fatalf("%d: bad formatting", lineNumber)
|
|
}
|
|
stackEntry := StructDefinition{scanner.Text(), context.generateQualifier(), nil}
|
|
context.stack = append(context.stack, &stackEntry)
|
|
return
|
|
case token == "enum":
|
|
if !scanner.Scan() || scanner.Text() != "class" || !scanner.Scan() {
|
|
log.Fatalf("%d: bad enum", lineNumber)
|
|
}
|
|
if !strings.Contains(codeline, "{") || strings.Contains(codeline, "}") {
|
|
log.Fatalf("%d: bad formatting", lineNumber)
|
|
}
|
|
stackEntry := EnumDefinition{scanner.Text(), context.generateQualifier(), nil}
|
|
context.stack = append(context.stack, &stackEntry)
|
|
return
|
|
case len(context.stack) > 1:
|
|
depth := len(context.stack) - 1
|
|
switch defn := context.stack[depth].(type) {
|
|
case *StructDefinition:
|
|
scanner.Scan()
|
|
defaultValueTokens := context.assignmentMatcher.FindStringSubmatch(codeline)
|
|
defaultValue := ""
|
|
if len(defaultValueTokens) > 0 {
|
|
defaultValue = defaultValueTokens[1]
|
|
}
|
|
defaultValue = context.distillValue(defaultValue, lineNumber)
|
|
var field StructField
|
|
skipJson := strings.Contains(codeline, `%codegen_skip_json%`)
|
|
skipJs := strings.Contains(codeline, `%codegen_skip_javascript%`)
|
|
if inPlaceDefinition != "" {
|
|
field = StructField{inPlaceDefinition, token, defaultValue, skipJson, skipJs}
|
|
} else {
|
|
field = StructField{token, scanner.Text(), defaultValue, skipJson, skipJs}
|
|
}
|
|
defn.Fields = append(defn.Fields, field)
|
|
return
|
|
case *EnumDefinition:
|
|
if strings.Contains(codeline, "=") {
|
|
log.Fatalf("%d: custom values are not allowed", lineNumber)
|
|
}
|
|
defn.Values = append(defn.Values, strings.Trim(token, ","))
|
|
return
|
|
}
|
|
}
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
log.Fatalf("%d: %s", lineNumber, err)
|
|
}
|
|
}
|