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

本文关键字:对象模型 引用 有效地 语法 ANTLR | 更新日期: 2023-09-27 18:21:11

让我们假设我是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
    :   [ 'r't'n]+ -> 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;
    }
}

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

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