如何在解析ANTLR语法后有效地解析对象模型中的引用
本文关键字:对象模型 引用 有效地 语法 ANTLR | 更新日期: 2023-09-27 18:21:11
让我们假设我是ANTLR来解析一些文本,以生成一个可以使用的只读对象模型。许多对象引用对象模型中的其他对象。
我目前正在采取的步骤是:
- 使用ANTLR 4将源解析为树(它生成)
- 遍历树以构建临时对象模型(使用字符串作为引用)
- 遍历临时对象模型并创建公共模型
这种方法的问题在于,随着语法的发展,类型和映射会激增。编译器和其他解析器采用什么方法来构建对象模型和解析内部引用?
来源
以下是正在解析的源代码的摘录。它被简化以说明挑战。
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;
}
}
如果在树上走两次,似乎可以避免使用临时模型。在第一次遍历中,创建只设置了名称的类实例,并将它们添加到字典中(类名,IClass)。在第二次迭代中,使用第一步中的字典设置它们之间的引用。