blob: 3384108032afab324a0a2922ab778d21e36c8933 [file]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package mojom
import (
"fmt"
)
// This file contains the types MojomFile and MojomDescriptor. These are the
// structures that are generated during parsing and then serialized and
// passed on to the backend of the Mojom Compiler.
///////////////////////////////////////////////////////////////////////
/// Type MojomFile
/// //////////////////////////////////////////////////////////////////
// A MojomFile represents the result of parsing a single .mojom file.
type MojomFile struct {
// The associated MojomDescriptor
Descriptor *MojomDescriptor
// The |CanonicalFileName| is the unique identifier for this module
// within the |MojomFilesByName| field of |Descriptor|
CanonicalFileName string
// The module namespace is the identifier declared via the "module"
// declaration in the .mojom file.
ModuleNamespace string
// Attributes declared in the Mojom file at the module level.
Attributes *Attributes
// The set of other MojomFiles imported by this one. The corresponding
// MojomFile may be obtained from the |MojomFilesByName| field of
// |Descriptor| using the |CanonicalFileName| field of ImportedFile.
Imports []*ImportedFile
// importsBySpecifiedName facilitates the lookup of an ImportedFile given its |SpecifiedName|
importsBySpecifiedName map[string]*ImportedFile
// The lexical scope corresponding to this file.
FileScope *Scope
// These are lists of *top-level* types and constants defined in the file;
// they do not include enums and constants defined within structs
// and interfaces. The contained enums and constant may be found in the
// |Enums| and |Constants| fields of their containing object.
Interfaces []*MojomInterface
Structs []*MojomStruct
Unions []*MojomUnion
Enums []*MojomEnum
Constants []*UserDefinedConstant
}
// An ImportedFile represents an element of the "import" list of a .mojom file.
type ImportedFile struct {
// The name as specified in the import statement.
SpecifiedName string
// The canonical file name of the imported file. This string is the unique identifier for the
// corresponding MojomFile object. Note that when a .mojom file is first parsed
// only the |SpecifiedFileName| of each of its imports is populated because we don't yet know the
// canonical file names of the imported files. It is only when an imported
// file itself is processed that |CanonicalFileName| field is populated within each of the
// importing MojomFiles.
CanonicalFileName string
}
func NewMojomFile(fileName string, descriptor *MojomDescriptor) *MojomFile {
mojomFile := new(MojomFile)
mojomFile.CanonicalFileName = fileName
mojomFile.Descriptor = descriptor
mojomFile.ModuleNamespace = ""
mojomFile.Imports = make([]*ImportedFile, 0)
mojomFile.importsBySpecifiedName = make(map[string]*ImportedFile)
mojomFile.Interfaces = make([]*MojomInterface, 0)
mojomFile.Structs = make([]*MojomStruct, 0)
mojomFile.Unions = make([]*MojomUnion, 0)
mojomFile.Enums = make([]*MojomEnum, 0)
mojomFile.Constants = make([]*UserDefinedConstant, 0)
return mojomFile
}
func (f *MojomFile) String() string {
s := fmt.Sprintf("file name: %s\n", f.CanonicalFileName)
s += fmt.Sprintf("module: %s\n", f.ModuleNamespace)
s += fmt.Sprintf("attributes: %s\n", f.Attributes)
s += fmt.Sprintf("imports: %s\n", f.Imports)
s += fmt.Sprintf("interfaces: %s\n", f.Interfaces)
s += fmt.Sprintf("structs: %s\n", f.Structs)
s += fmt.Sprintf("enums: %s\n", f.Enums)
s += fmt.Sprintf("constants: %s\n", f.Constants)
return s
}
func (f *MojomFile) SetModuleNamespace(namespace string) *Scope {
f.ModuleNamespace = namespace
f.FileScope = NewLexicalScope(ScopeFileModule, nil, namespace, f)
return f.FileScope
}
func (f *MojomFile) AddImport(specifiedFileName string) {
importedFile := new(ImportedFile)
importedFile.SpecifiedName = specifiedFileName
f.Imports = append(f.Imports, importedFile)
f.importsBySpecifiedName[specifiedFileName] = importedFile
}
// SetCanonicalImportName sets the |CanonicalFileName| field of the |ImportedFile|
// with the given |SpecifiedName|. This method will usually be invoked later than
// the other methods in this file becuase it is only when the imported file itself
// is processed that we discover its canonical name.
func (f *MojomFile) SetCanonicalImportName(specifiedName, canoncialName string) {
importedFile, ok := f.importsBySpecifiedName[specifiedName]
if !ok {
panic(fmt.Sprintf("There is no imported file with the specifiedName '%s'.", specifiedName))
}
importedFile.CanonicalFileName = canoncialName
}
func (f *MojomFile) AddInterface(mojomInterface *MojomInterface) *DuplicateNameError {
f.Interfaces = append(f.Interfaces, mojomInterface)
return mojomInterface.RegisterInScope(f.FileScope)
}
func (f *MojomFile) AddStruct(mojomStruct *MojomStruct) *DuplicateNameError {
f.Structs = append(f.Structs, mojomStruct)
return mojomStruct.RegisterInScope(f.FileScope)
}
func (f *MojomFile) AddEnum(mojomEnum *MojomEnum) *DuplicateNameError {
f.Enums = append(f.Enums, mojomEnum)
return mojomEnum.RegisterInScope(f.FileScope)
}
func (f *MojomFile) AddUnion(mojomUnion *MojomUnion) *DuplicateNameError {
f.Unions = append(f.Unions, mojomUnion)
return mojomUnion.RegisterInScope(f.FileScope)
}
func (f *MojomFile) AddConstant(declaredConst *UserDefinedConstant) *DuplicateNameError {
f.Constants = append(f.Constants, declaredConst)
return declaredConst.RegisterInScope(f.FileScope)
}
//////////////////////////////////////////////////////////////////
/// type MojomDescriptor
/// //////////////////////////////////////////////////////////////
// A MojomDescriptor is the central object being populated by the frontend of
// the Mojom compiler. The same instance of MojomDescriptor is passed to each
// of the instances of Parser that are created by the ParseDriver while parsing
// a graph of Mojom files. The output of ParserDriver.ParseFiles() is a
// ParseResult the main field of which is a MojomDescriptor. The MojomDescriptor
// is then serialized and passed to the backend of the Mojom compiler.
type MojomDescriptor struct {
// All of the UserDefinedTypes keyed by type key
TypesByKey map[string]UserDefinedType
// All of the UserDefinedValues keyed by value key
ValuesByKey map[string]UserDefinedValue
// All of the MojomFiles in the order they were visited.
mojomFiles []*MojomFile
// All of the MojomFiles keyed by CanonicalFileName
MojomFilesByName map[string]*MojomFile
// The abstract module namespace scopes keyed by scope name. These are
// the scopes that are not lexical scopes (i.e. files, structs, interfaces)
// but rather the ancestor's of the file scopes that are implicit in the
// dotted name of the file's module namespace. For example if a file's
// module namespace is "foo.bar" then the chain of ancestors of the
// file scope is as follows:
// [file "foo.bar"] -> [module "foo.bar"] -> [module "foo"] -> [module ""]
// The field |abstractScopesByName| will contain the last three of these
// scopes but not the first. The last scope [module ""] is called the
// global scope. The reason for both a [file "foo.bar"] and a
// [module "foo.bar"] is that multiple files might have a module namespace
// that is a descendent of [module "foo.bar"].
abstractScopesByName map[string]*Scope
// When new type and value references are encountered during parsing they
// are added to these slices. After parsing completes the resolution
// step attempts to resolve all of these references. If these slices are
// not empty by the time ParserDriver.ParseFiles() completes it means that
// there are unresolved references in the .mojom files.
unresolvedTypeReferences []*UserTypeRef
unresolvedValueReferences []*UserValueRef
}
func NewMojomDescriptor() *MojomDescriptor {
descriptor := new(MojomDescriptor)
descriptor.TypesByKey = make(map[string]UserDefinedType)
descriptor.ValuesByKey = make(map[string]UserDefinedValue)
descriptor.mojomFiles = make([]*MojomFile, 0)
descriptor.MojomFilesByName = make(map[string]*MojomFile)
descriptor.abstractScopesByName = make(map[string]*Scope)
// The global namespace scope.
descriptor.abstractScopesByName[""] = NewAbstractModuleScope("", descriptor)
descriptor.unresolvedTypeReferences = make([]*UserTypeRef, 0)
descriptor.unresolvedValueReferences = make([]*UserValueRef, 0)
return descriptor
}
func (d *MojomDescriptor) getAbstractModuleScope(fullyQualifiedName string) *Scope {
if scope, ok := d.abstractScopesByName[fullyQualifiedName]; ok {
return scope
}
scope := NewAbstractModuleScope(fullyQualifiedName, d)
d.abstractScopesByName[fullyQualifiedName] = scope
return scope
}
func (d *MojomDescriptor) getGlobalScobe() *Scope {
return d.abstractScopesByName[""]
}
func (d *MojomDescriptor) AddMojomFile(fileName string) *MojomFile {
mojomFile := NewMojomFile(fileName, d)
mojomFile.Descriptor = d
d.mojomFiles = append(d.mojomFiles, mojomFile)
if _, ok := d.MojomFilesByName[mojomFile.CanonicalFileName]; ok {
panic(fmt.Sprintf("The file %v has already been processed.", mojomFile.CanonicalFileName))
}
d.MojomFilesByName[mojomFile.CanonicalFileName] = mojomFile
return mojomFile
}
func (d *MojomDescriptor) RegisterUnresolvedTypeReference(typeReference *UserTypeRef) {
d.unresolvedTypeReferences = append(d.unresolvedTypeReferences, typeReference)
}
func (d *MojomDescriptor) RegisterUnresolvedValueReference(valueReference *UserValueRef) {
d.unresolvedValueReferences = append(d.unresolvedValueReferences, valueReference)
}
func (d *MojomDescriptor) ContainsFile(fileName string) bool {
_, ok := d.MojomFilesByName[fileName]
return ok
}
/////////////////////////////////////////
/// Type and Value Resolution
////////////////////////////////////////
// Resolve() should be invoked after all of the parsing has been done. It
// attempts to resolve all of the entries in |d.unresolvedTypeReferences| and
// |d.unresolvedValueReferences|. Returns a non-nil error if there are any
// remaining unresolved references or if after resolution it was discovered
// that a type or value was used in an inappropriate way.
func (d *MojomDescriptor) Resolve() error {
// Resolve the types
unresolvedTypeReferences, err := d.resolveTypeReferences()
if err != nil {
// For one of the type references we discovered after resolution that
// the resolved type was used in an inappropriate way.
return err
}
numUnresolvedTypeReferences := len(unresolvedTypeReferences)
// Resolve the values
unresolvedValueReferences, err := d.resolveValueReferences()
if err != nil {
// For one of the value references we discovered after resolution that
// the resolved value was used in an inappropriate way.
return err
}
numUnresolvedValueReferences := len(unresolvedValueReferences)
// Because values may be defined in terms of user-defined constants which
// may themselves be defined in terms of other user-defined constants,
// we may have to perform the value resolution step multiple times in
// order to propogage concrete values to all declarations. To make sure that
// this process terminates we keep iterating only as long as the number
// of unresolved value references decreases.
for numUnresolvedValueReferences > 0 {
unresolvedValueReferences, _ = d.resolveValueReferences()
if len(unresolvedValueReferences) < numUnresolvedValueReferences {
numUnresolvedValueReferences = len(unresolvedValueReferences)
} else {
break
}
}
d.unresolvedTypeReferences = unresolvedTypeReferences[0:numUnresolvedTypeReferences]
d.unresolvedValueReferences = unresolvedValueReferences[0:numUnresolvedValueReferences]
if numUnresolvedTypeReferences+numUnresolvedValueReferences == 0 {
return nil
}
errorMessage := "There are still some unresolved references.\n"
if numUnresolvedTypeReferences > 0 {
errorMessage += "\nNo defintion found for the following types:\n"
errorMessage += "-------------------------------------------------------\n"
for _, ref := range d.unresolvedTypeReferences {
errorMessage += fmt.Sprintf("%s\n", ref.LongString())
}
}
if numUnresolvedValueReferences > 0 {
errorMessage += "\nNo defintion found for the following values:\n"
errorMessage += "-----------------------------------------------------------\n"
for _, ref := range d.unresolvedValueReferences {
errorMessage += fmt.Sprintf("%s\n", ref.LongString())
}
}
return fmt.Errorf(errorMessage)
}
func (d *MojomDescriptor) resolveTypeReferences() (unresolvedReferences []*UserTypeRef, postResolutionValidationError error) {
unresolvedReferences = make([]*UserTypeRef, len(d.unresolvedTypeReferences))
numUnresolved := 0
for _, ref := range d.unresolvedTypeReferences {
if ref != nil {
if !d.resolveTypeRef(ref) {
unresolvedReferences[numUnresolved] = ref
numUnresolved++
} else {
if postResolutionValidationError = ref.validateAfterResolution(); postResolutionValidationError != nil {
break
}
}
}
}
unresolvedReferences = unresolvedReferences[0:numUnresolved]
return
}
func (d *MojomDescriptor) resolveValueReferences() (unresolvedReferences []*UserValueRef, postResolutionValidationError error) {
unresolvedReferences = make([]*UserValueRef, len(d.unresolvedValueReferences))
numUnresolved := 0
for _, ref := range d.unresolvedValueReferences {
if ref != nil {
if !d.resolveValueRef(ref) {
unresolvedReferences[numUnresolved] = ref
numUnresolved++
} else {
if postResolutionValidationError = ref.validateAfterResolution(); postResolutionValidationError != nil {
break
}
}
}
}
unresolvedReferences = unresolvedReferences[0:numUnresolved]
return
}
func (d *MojomDescriptor) resolveTypeRef(ref *UserTypeRef) (success bool) {
ref.resolvedType = ref.scope.LookupType(ref.identifier)
return ref.resolvedType != nil
}
// There are two steps to resolving a value. First resolve the identifier to
// to a target declaration, then resolve the target declaration to a
// concrte value.
func (d *MojomDescriptor) resolveValueRef(ref *UserValueRef) (resolved bool) {
// Step 1: Find resolvedDeclaredValue
if ref.resolvedDeclaredValue == nil {
userDefinedValue := ref.scope.LookupValue(ref.identifier, ref.assigneeType)
if userDefinedValue == nil {
lookupValue, ok := LookupBuiltInConstantValue(ref.identifier)
if !ok {
return false
}
userDefinedValue = lookupValue
}
ref.resolvedDeclaredValue = userDefinedValue
}
// Step 2: Find resolvedConcreteValue.
switch value := ref.resolvedDeclaredValue.(type) {
case *UserDefinedConstant:
// The identifier resolved to a user-declared constant. We use
// the (possibly nil) resolved value of that constant as
// resolvedConcreteValue. Since this may be nil it is possible that
// ref is still unresolved.
ref.resolvedConcreteValue = value.valueRef.ResolvedConcreteValue()
case *EnumValue:
// The identifier resolved to an enum value. We use the enum value
// itself (not the integer value of the enum value) as the
// resolvedConcreteValue. Since this cannot be nil we know that
// ref is now fully resolved.
ref.resolvedConcreteValue = value
case *BuiltInConstantValue:
// The identifier resolved to a built-in. We use the built-in value
// itself (not the integer value of the enum value) as the
// resolvedConcreteValue. Since this cannot be nil we know that
// ref is now fully resolved.
ref.resolvedConcreteValue = value
default:
panic("ref.resolvedDeclaredValue is neither a DelcaredConstant, an EnumValue, nor a BuiltInConstantValue.")
}
return ref.resolvedConcreteValue != nil
}
//////////////////////////////////////
// Debug printing of a MojomDescriptor
//////////////////////////////////////
func (d *MojomDescriptor) debugPrintMojomFiles() (s string) {
for _, f := range d.mojomFiles {
s += fmt.Sprintf("\n%s\n", f)
}
return
}
func (d *MojomDescriptor) debugPrintUnresolvedTypeReference() (s string) {
for _, r := range d.unresolvedTypeReferences {
s += fmt.Sprintf("%s\n", r.LongString())
}
return
}
func debugPrintTypeMap(m map[string]UserDefinedType) (s string) {
for key, value := range m {
s += fmt.Sprintf("%s : %s %s\n", key, value.SimpleName(), value.Kind())
}
return
}
func debugPrintValueMap(m map[string]UserDefinedValue) (s string) {
for key, value := range m {
s += fmt.Sprintf("%s : %s\n", key, value.SimpleName())
}
return
}
func (d *MojomDescriptor) String() string {
message :=
`
TypesByKey:
----------------
%s
ValuesByKey:
----------------
%s
Files:
---------------
%s
`
return fmt.Sprintf(message, debugPrintTypeMap(d.TypesByKey), debugPrintValueMap(d.ValuesByKey), d.debugPrintMojomFiles())
}
///////////////////////////////////////////////////////////////////////
/// Miscelleneous utilities
/// //////////////////////////////////////////////////////////////////
func computeTypeKey(fullyQualifiedName string) (typeKey string) {
if typeKey, ok := fqnToTypeKey[fullyQualifiedName]; ok == true {
return typeKey
}
typeKey = fmt.Sprintf("%d", nextKey)
nextKey++
fqnToTypeKey[fullyQualifiedName] = typeKey
return
}
var fqnToTypeKey map[string]string
var nextKey int
func init() {
fqnToTypeKey = make(map[string]string)
}