如何在解析ANTLR语法后有效地解析对象模型中的引用



让我们假设我是ANTLR来解析一些文本,以生成一个可以使用的只读对象模型。许多对象引用对象模型中的其他对象。

我目前正在采取的步骤是:

  1. 使用ANTLR 4将源解析为树(它生成)
  2. 遍历树以构建临时对象模型(使用字符串作为引用)
  3. 遍历临时对象模型并创建公共模型

这种方法的问题在于,随着语法的发展,类型和映射会激增。编译器和其他解析器采用什么方法来构建对象模型和解析内部引用?

来源

以下是正在解析的源代码的摘录。它被简化以说明挑战。

class Class1 : Class2, Class4
{
}
class Class2 : Class3
{
}
class Class3
{
}
class Class4
{
}

公共对象模型

下面是公共对象模型,它是解析的结果。

public class ModelFactory
{
    public IModel Create()
    {
        /* Some magic */
    }
}
public interface IModel
{
    IClass CreateClass(string name);
    IEnumerable<IClass> Classes
    {
        get;
    }
}
public interface IClass
{
    void CreateGeneralization(IClass superClass);
    IEnumerable<IClass> SubClasses
    {
        get;
    }
    IEnumerable<IClass> SuperClasses
    {
        get;
    }
    IModel Model
    {
        get;
    }
    string Name
    {
        get;
    }
}

测试

我写了一个测试来验证我做对了:

[Test]
public void ParseTest()
{
    // Arrange
    const string path = "MultipleInheritance.txt";
    var target = new ModelParser();
    // Act
    var model = target.Parse(path);
    // Assert
    Assert.IsNotNull(model);
    Assert.IsNotNull(model.Classes);
    var class1 = model.Classes.FirstOrDefault(c => c.Name == "Class1");
    var class2 = model.Classes.FirstOrDefault(c => c.Name == "Class2");
    var class3 = model.Classes.FirstOrDefault(c => c.Name == "Class3");
    var class4 = model.Classes.FirstOrDefault(c => c.Name == "Class4");            
    Assert.IsNotNull(class1);
    Assert.IsNotNull(class2);
    Assert.IsNotNull(class3);
    Assert.IsNotNull(class4);
    Assert.IsTrue(class1.SuperClasses.Any(c => c == class2));
    Assert.IsTrue(class1.SuperClasses.Any(c => c == class4));
    Assert.IsTrue(class2.SuperClasses.Any(c => c == class3));
    Assert.IsEmpty(class3.SuperClasses);
    Assert.IsEmpty(class4.SuperClasses);
    Assert.IsTrue(class4.SubClasses.Any(c => c == class1));
}

语法

为了说明这个问题,语法被简化了。

grammar Model;
/*
 * Parser Rules
 */
model
    :   classDeclaration*
    |   EOF
    ;
classDeclaration
    :   'class' name=Identifier  (':' generalizations=typeList)?
        '{' 
        /* attributeDeclaration* */
        '}'
    ;
typeList
    :   type (',' type)*
    ;
type
    :   name=Identifier
    ;
/*
 * Lexer Rules
 */
Identifier
    :   Letter (Letter|IdentifierDigit)*
    ;
fragment
Letter
    :   'u0024' 
    |   'u0041'..'u005a' 
    |   'u005f' 
    |   'u0061'..'u007a' 
    |   'u00c0'..'u00d6' 
    |   'u00d8'..'u00f6' 
    |   'u00f8'..'u00ff' 
    |   'u0100'..'u1fff' 
    |   'u3040'..'u318f' 
    |   'u3300'..'u337f' 
    |   'u3400'..'u3d2d' 
    |   'u4e00'..'u9fff' 
    |   'uf900'..'ufaff'
    ;
fragment
IdentifierDigit
    :   'u0030'..'u0039' 
    |   'u0660'..'u0669' 
    |   'u06f0'..'u06f9' 
    |   'u0966'..'u096f' 
    |   'u09e6'..'u09ef' 
    |   'u0a66'..'u0a6f' 
    |   'u0ae6'..'u0aef' 
    |   'u0b66'..'u0b6f' 
    |   'u0be7'..'u0bef' 
    |   'u0c66'..'u0c6f' 
    |   'u0ce6'..'u0cef' 
    |   'u0d66'..'u0d6f' 
    |   'u0e50'..'u0e59' 
    |   'u0ed0'..'u0ed9' 
    |   'u1040'..'u1049'
    ;
WS
    :   [ rtn]+ -> skip 
    ;

临时对象模型

解析后,我从树中构建这个模型,然后步行构建公共域模型。

public class TempoaryModel
{
    public TempoaryModel()
    {
        Classes = new List<TemporaryClass>();
    }
    public List<TemporaryClass> Classes
    {
        get;
        private set;
    }
}
public class TemporaryClass
{
    public TemporaryClass()
    {
        SuperClasses = new List<string>();
    }
    public List<string> SuperClasses
    {
        get;
        private set;
    }
    public string Name
    {
        get;
        set;
    }
}

如果在树上走两次,似乎可以避免使用临时模型。在第一次遍历中,创建只设置了名称的类实例,并将它们添加到字典中(类名,IClass)。在第二次迭代中,使用第一步中的字典设置它们之间的引用。

相关内容

  • 没有找到相关文章

最新更新