让我们假设我是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
: [ 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)。在第二次迭代中,使用第一步中的字典设置它们之间的引用。