Chapter 13. Modules

An Mg module is a scope that contains declarations of languages (Chapter 12). Declarations exported by an imported module are made available in the importing module. Thus, modules override lexical scoping that otherwise governs Mg symbol resolution. Modules themselves do not nest.

Compilation Unit

Several modules may be contained within a CompilationUnit, typically a text file.

CompilationUnit:

 

ModuleDeclarations

ModuleDeclarations:

 

ModuleDeclaration

 

ModuleDeclarations ModuleDeclaration

Module Declaration

A ModuleDeclaration is a named container/scope for language declarations.

ModuleDeclaration:
  module QualifiedIdentifer ModuleBody ;opt
QualifiedIdentifier:
  Identifier
  QualifiedIdentifier . Identifier
ModuleBody:
  { ImportDirectives  ExportDirectives  ModuleMemberDeclarations }
ModuleMemberDeclarations:
  ModuleMemberDeclaration
  ModuleMemberDeclarations ModuleMemberDeclaration
ModuleMemberDeclaration:
  LanguageDeclaration

Each ModuleDeclaration has a QualifiedIdentifier that uniquely qualifies the declarations contained by the module.

Each ModuleMemberDeclaration may be referenced either by its Identifier or by its fully qualified name by concatenating the QualifiedIdentifier of the ModuleDeclaration with the Identifier of the ModuleMemberDeclaration (separated by a period).

For example, given the following ModuleDeclaration:

module BaseDefinitions {
    export Logical;
    language Logical {
        syntax Literal = "true" | "false";
    }
}

The fully qualified name of the language is BaseDefinitions.Logical, or using escaped identifiers, [BaseDefinitions].[Logical]. It is always legal to use a fully qualified name where the name of a declaration is expected.

Modules are not hierarchical or nested. That is, there is no implied relationship between modules whose QualifiedIdentifier share a common prefix.

For example, consider these two declarations:

module A {
    language L {
        token I = ('0'..'9')+;
    }
}
module A.B {
   language M {
       token D = L.I '.' L.I;
   }
}

Module A.B is in error, as it does not contain a declaration for the identifier L. That is, the members of Module A are not implicitly imported into Module A.B.

Inter-Module Dependencies

Mg uses ImportDirectives and ExportDirectives to explicitly control which declarations may be used across module boundaries.

ExportDirectives:
  ExportDirective
  ExportDirectives ExportDirective
ExportDirective:
  export Identifiers ;
ImportDirectives:
  ImportDirective
  ImportDirectives ImportDirective
ImportDirective:
  import ImportModules ;
  import QualifiedIdentifier  { ImportMembers }  ;
ImportMember:
  Identifier  ImportAliasopt
ImportMembers:
  ImportMember
  ImportMembers , ImportMember
ImportModule:
  QualifiedIdentifier  ImportAliasopt
ImportModules:
  ImportModule
  ImportModules  , ImportModule
ImportAlias:
  as Identifier

A ModuleDeclaration contains zero or more ExportDirectives, each of which makes a ModuleMemberDeclaration available to declarations outside of the current module.

A ModuleDeclaration contains zero or more ImportDirectives, each of which names a ModuleDeclaration whose declarations may be referenced by the current module.

A ModuleMemberDeclaration may only reference declarations in the current module and declarations that have an explicit ImportDirective in the current module.

An ImportDirective is not transitive, that is, importing module A does not import the modules that A imports.

For example, consider this ModuleDeclaration:

module Language.Core {
    export Base;

    language Internal {
        token Digit = '0'..'9';
        token Letter = 'A'..'Z' | 'a'..'z';
    }

    language Base {
        token Identifier = Letter (Letter | Digit)*;
    }
}

The definition Language.Core.Internal may be referenced only from within the module Language.Core. The definition Language.Core.Base may be referenced in any module that has an ImportDirective for module Language.Core, as shown in this example:

module Language.Extensions {
    import Language.Core;
    language Names {
        syntax QualifiedIdentifier
          = Language.Core.Base.Identifier '.'
Language.Core.Base.Identifier;
    }
}

The preceding example uses the fully qualified name to refer to Language.Core.Base. An ImportDirective may also specify an ImportAlias that provides a replacement Identifier for the imported declaration:

module Language.Extensions {
    import Language.Core as lc;

    language Names {
        syntax QualifiedIdentifier
          = lc.Base.Identifier '.' lc.Base.Identifier;
    }
}

An ImportAlias replaces the name of the imported declaration. This means that the following is an error:

module Language.Extensions {
    import Language.Core as lc;

    language Names {
        syntax QualifiedIdentifier
          = Language.Core.Base.Identifier '.'
Language.Core.Base.Identifier;
    }
}

It is legal for two or more ImportDirectives to import the same declaration, provided they specify distinct aliases. For a given compilation episode, at most one ImportDirective may use a given alias.

If an ImportDirective imports a module without specifying an alias, the declarations in the imported module may be referenced without the qualification of the module name.

That means the following is also legal.

module Language.Extensions {
    import Language.Core;

    language Names {
        syntax QualifiedIdentifier = Base.Identifier '.' Base.Identifier;
    }
}

When two modules contain same-named declarations, there is a potential for ambiguity. The potential for ambiguity is not an error—ambiguity errors are detected lazily as part of resolving references.

Consider the following two modules:

module A {
    export L;
    language L {
        token X = '1';
    }
}
module B {
    export L;
    language L {
        token X = '2';
    }
}

It is legal to import both modules either with or without providing an alias:

module C {
    import A, B;
    language M {
        token Y = '3';
    }
}

This is legal because ambiguity is only an error for references, not declarations. This means that the following is a compile-time error:

module C {
    import A, B;
    language M {
        token Y = L.X | '3';
    }
}

This example can be made legal either by fully qualifying the reference to L:

module C {
    import A, B;
    language M {
        token Y = A.L.X | '3';   // no error
    }
}

or by adding an alias to one or both of the ImportDirectives:

module C {
    import A;
    import B as bb;
    language M {
        token Y = L.X | '3';   // no error, refers to A.L
        token Z = bb.L.X | '3';   // no error, refers to B.L
    }
}

An ImportDirective may either import all exported declarations from a module or only a selected subset of them. The latter is enabled by specifying ImportMembers as part of the directive. For example, Module Plot2D imports only Point2D and PointPolar from the Module Geometry:

module Geometry {
    import Algebra;
    export Geo2D, Geo3D;
    language Geo2D {
        syntax Point = '(' Numbers.Number ',' Numbers.Number ')';
        syntax PointPolar = '<' Numbers.Number ',' Numbers.Number '>';
    }
    language Geo3D {
        syntax Point =
             '(' Numbers.Number ',' Numbers.Number ',' Numbers.Number ')';
    }
}
module Plot2D {
    import Geometry {Geo2D};
    language Paths {
        syntax Path = '(' Geo2D.Point* ')';
        syntax PathPolar = '(' Geo2D.PointPolar* ')';
    }
}

An ImportDirective that contains an ImportMember imports only the named declarations from that module. This means that the following is a compilation error because module Plot3D references Geo3D which is not imported from module Geometry:

module Plot3D {
    import Geometry {Geo2D};
    language Paths {
        syntax Path = '(' Geo3D.Point* ')';
    }
}

An ImportDirective that contains an ImportAlias on a selected imported member assigns the replacement name to the imported declaration, hiding the original export name.

module Plot3D {
    import Geometry {Geo3D as geo};
    language Paths {
        syntax Path = '(' geo.Point* ')';
    }
}

Aliasing an individual imported member is useful to resolve occasional conflicts between imports. Aliasing an entire imported module is useful to resolve a systemic conflict. For example, when importing two modules, where one is a different version of the other, it is likely to get many conflicts. Aliasing at member level would lead to a correspondingly long list of alias declarations.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.237.79