通用对象编译器





5.00/5 (4投票s)
由您设计的任何语言的源代码驱动的对象创建
引言
BNFUP是一个类库,它实现了一个对象编译器,您可以将其链接到自己的项目中,以便用户能够使用您设计的语言输入一些源代码,并通过编译它们来创建实用对象。例如,可以考虑创建算术表达式或使用脚本进行复杂的数据输入。
该库还提供编辑服务,以便您创建自己的工具来设计和编辑语言。它附带了一个名为BNFUPEditor的项目中的编辑器,让您无需从头开始编写任何代码即可开始使用编辑功能。
还有三个实现示例,您可以用来理解如何将您自己的项目与编译器服务链接。
背景
要使用编译器,首先需要设计一个类库和一种语言,您可以使用它来创建对象,并通过BNF规则来实现。使用BNFUPEditor工具,您可以设计语言并使用内置编译器进行测试。
由于本文将重点放在库的源代码上,您可以在我的博客上阅读有关编辑器使用的内容:使用BNFUPEditor工具。在这里您可以阅读同一篇文章的西班牙语版本。
在解决方案的Samples子目录中,您可以找到一些语言示例,对应于Expressions、ExpressionDrawer和CompilableDataTable项目中实现的三个示例。
该项目使用C#和Visual Studio 2015编写。
使用代码
让我们从定义您用来编写自己的语言的语法的BNF格式开始。
<<rulelist>> ::= <rule> [<rulelist>]
<rule> ::= <rulename> '::=' <defrule> ';'
<rulename> ::= <ruleid>
| '<<' <identifier> '>>'
<defrule> ::= <defrulech> ['|' <defrule>]
<defrulech> ::= <item> [<defrulech>]
<ruleid> ::= '<' <identifier> '>'
<identifier> ::= {a-zA-Z} [<rid>]
<rid> ::= {a-zA-Z0-9} [<rid>]
<item> ::= <token>
| <ltoken>
| <charset>
| <ruleid>
| '[' <item> ']'
<token> ::= ''' {.} [<rtoken>] '''
| ''' '\'' [<rtoken>] '''
<rtoken> ::= {.} [<rtoken>]
| '\'' [<rtoken>]
<ltoken> ::= <token> [',' <ltoken>]
<charset> ::= '{' <charlist> '}'
<charlist> ::= '\}' [<charlist>]
| <char> [<charlist>]
您可以看到,所有规则都必须以分号结尾。您可以使用方括号将规则的一部分标记为可选,并使用字符|来分隔它们,从而创建由一些替代规则组成的规则。
您可以使用单引号定义标记,使用花括号和正则表达式字符集语法定义字符集。
规则名称用字符<和>括起来。主规则,代码解析从此开始,用双字符<<和>>标记。
规则与其定义之间用标记::=分隔。
您可以在规则定义中使用的元素如下:
- 标记 (token):一个单词,它必须出现在源代码的相应位置。
- 标记列表 (List of tokens):一组标记,其中必须出现一个且仅一个标记,出现在源代码的相应位置。
- 字符集 (Character sets):它定义了一组可以出现在源代码相应位置的字符。
- 规则名称 (Rule names):您可以使用语言中定义的任何规则,也可以使用您正在定义的规则,以允许递归定义。
您可以将语言定义写在文本文件中,然后用规则编辑器打开它,该编辑器会将文本转换为应用程序的内部格式,并允许您将其保存为最终的二进制格式。使用编辑器,您还可以定义哪些规则或规则元素将生成对象。
当达到生成对象的项目时,对象将以未初始化的状态创建。当元素完全解析后,通过将源代码的相应部分传递给对象来完成对象(例如,考虑一个数字,它是逐个字符解析的,当数字文本完成时,编译器将其传递给数字对象以设置其值)。
如果在创建了另一个对象的规则定义中创建了新对象,则该对象被视为从属对象,并在完成后传递给主对象。
要将您自己的对象与编译器引擎链接,您必须在代码中实现一些简单的接口。这些接口定义在BNFUP.Interfaces命名空间中。
所有可编译对象都必须实现的接口是ICompilableObject,定义如下:
public interface ICompilableObject
{
string Text { get; set; }
bool AddItem(ICompilableObject item);
ICompilableObject Simplify();
void Test(Form fmain);
}
通过Text属性,编译器将向对象提供生成它的源代码部分。
通过AddItem方法,编译器将向对象提供另一个从属对象,该对象在主对象完成之前在规则中创建。如果对象被接受,则对象返回true,否则返回false,这将导致编译错误。
通过Simplify方法,您可以返回对象的简化版本,或者如果不需要简化,则返回对象本身。此方法在对象完成后调用。
最后,可以实现Test方法,以允许在您编写的编译器工具或BNFUPEditor工具的内置编译器中测试对象。
您的类库必须有一个实现ICompilableObjectFactory接口的类,该接口用于创建对象并将其提供给编译器。这些类必须有一个不带参数的构造函数。
public interface ICompilableObjectFactory
{
ICompilableObject Object { get; }
void Init();
ICompilableObject CreateObject(string ctype, IRuleItem item);
}
Object属性用于存储编译过程创建的最终对象。
使用Init方法在编译之前执行所需的初始化。
CreateObject方法在编译器必须创建对象时被调用。ctype参数是待创建对象类型的唯一标识符,item参数是触发对象创建的项目。
您可以使用BNFUP.Rules命名空间中定义的RuleTable类来使用编译器。RuleTable.FromFile静态方法将使用BNFUPEditor工具生成的.bnf文件创建该类的实例。
要将编译器与您的类库链接,请将RuleTable实例的Factory属性初始化为ICompilableObjectFactory对象。
最后,使用RuleTable实例的Build方法来编译源代码,源代码通过泛型TextReader传递,因此您可以使用文本文件或用户在控件中键入的文本。
在此链接中,您可以看到BNFUP.dll与您自己的类库集成示例。这里是西班牙语版本。
编译器实现
编译器是通过BNFUP.Rules命名空间中的一些类实现的。构成语言基础设施的项目类型及其实现的类如下:
- Token和character sets:这些是原子元素。它们实现在Token和CharacterSet类中,这两个类都派生自一个名为ItemBase的公共抽象基类。
- TokenList:一个只能包含Token类元素的列表。实现了TokenList类。允许定义一组标记,其中必须有一个出现在源代码的相应位置。
- ItemList:这是一个通用元素列表,可以包含任何类型的元素。它们的主要功能是分组元素,以便您可以将所有元素标记为可选,方法是将列表标记为可选,而不是将元素本身标记为可选,这将在语言定义中它们出现的任何地方都使其成为可选。它实现在RuleItemList类中。
- Rules:一种特殊的项目列表。有两种规则:简单规则,类似于项目列表,并在RuleChain类中实现;以及由一些替代规则组成的规则,这些规则只能包含RuleChain类型的对象,并在AlternativeRules类中实现。这两个类都派生自抽象基类RuleBase。
所有这些都实现了BNFUP.Interfaces命名空间中的IRuleItem接口,定义如下:
public interface IRuleItem
{
bool Completed { get; set; }
bool Optional { get; set; }
string Description { get; }
IEnumerable<IRuleItem> Childs { get; }
string GetText();
void UpdateRule(string name, RuleBase rule, string current);
void ChangeRule(RuleBase oldrule, RuleBase newrule, string current);
void DeleteRule(RuleBase rule, string current);
bool Matches(Parser parser);
void Build(Parser parser);
}
Completed是一个内部属性,用于将项目标记为与编译器的事件系统链接,或通过将其值设置为false来解除链接。
Optional用于将项目标记为可选。可选项目可以在源代码的相应位置出现,也可以不出现。
Description:包含项目描述。
Childs:枚举子项目(如果有)。
GetText:用于获取生成项目的源代码部分的方法。
UpdateRule:在构建对象期间,当规则从文本文件中读取时,用于更新规则的内部方法。如果一个规则尚未定义,但它是当前正在构建的规则的一部分,则使用一个特殊类TempRule。当文本文件处理结束时,RuleTable对象使用此方法用规则的真实版本更新这些规则。
ChangeRule:用于在当前项目中更改一个规则为另一个规则。
DeleteRule:删除规则在当前项目中的使用,但不会从规则表中删除规则。
Matches:用于测试项目是否在当前源代码位置匹配。Parser类控制源代码位置,并提供标记和字符集的测试。
Build:构建当前项目,并使Parser在源代码中前进,或者在找不到匹配项时引发异常。
由于所有项目类型都可以创建对象,因此还有一个为此任务定义的接口。ICompilableObjectGenartor,在BNFUP.Interfaces命名空间中。
public delegate void ObjectCreatedHandler(ICompilableObjectGenerator item);
public interface ICompilableObjectGenerator
{
event ObjectCreatedHandler OnCreateObject;
event ObjectCreatedHandler OnCompleteObject;
event ObjectCreatedHandler OnDiscardObject;
string CompilableType { get; set; }
ICompilableObjectFactory Factory { get; set; }
ICompilableObject Object { get; }
void UpdateObject(ICompilableObject obj);
}
有三个编译事件,用于在对象创建时(项目开始其构建过程)、完成时(项目结束解析代码)或丢弃时(如果项目是可选且不匹配源代码)通知RuleTable。
CompilableType:是此项目生成的对象的唯一标识符。此属性没有值的项目不生成任何对象。
Factory:生成对象的ICompilableObjectFactory。
Object:包含项目生成的当前编译对象。
UpdateObject:用于更新项目对象版本。
为了提供编辑服务,还有一个IEditableItem接口,定义在BNFUP.Interfaces命名空间中。
public enum EditActions
{
Delete, Cut, Copy, Paste, Enlist, Extract, Free, Up, Down, New, Simplify, Alternatives
}
public interface IEditableItem
{
bool CanCopy { get; }
int ItemIndex { get; set; }
IEnumerable<ToolStripItem> EditOptions(IEditableItem parent, IEditableItem gparent, IRuleItemClipboard clipboard);
bool PerformAction(ToolStripItem action, IEditableItem parent, IEditableItem gparent, RuleTable rules, IRuleItemClipboard clipboard);
void AddChild(IRuleItem item);
void RemoveChild(int item);
void ReplaceChild(int item1, IRuleItem item2);
void InsertAfter(int item1, IRuleItem item2);
void MoveUp(int item);
void MoveDown(int item);
bool ValidateType(Type t, bool generic);
}
CanCopy:内部用于显示或不显示剪切、复制和粘贴对象的选项。
ItemIndex:项目在其父对象中的索引。
EditOptions:枚举允许编辑当前项目的所有选项,作为带有相应EditActions枚举值在其Tag属性中的ToolStripItem对象。
PerformAction:用于执行选定的编辑操作。
ValidateType:用于允许或拒绝一种类型的对象作为有效的子项。泛型参数指示类型是已存在对象还是泛型类的未初始化版本。
其他方法用于管理子项目列表或项目在父列表中的位置。
其他辅助接口
在BNFUP.Interfaces命名空间中有三个额外的辅助接口:
public interface IItemDrawing
{
Color ForeColor { get; set; }
SizeF Measure(Graphics gr, Font fextra, Font fmain);
void Draw(Graphics gr, float x, float y, Font fextra, Font fmain);
}
public interface IRuleDrawing
{
SizeF MeasureRule(Graphics gr, Font fextra, Font fmain);
void DrawRule(Graphics gr, float x, float y, Font fextra, Font fmain);
}
public interface IRuleItemClipboard
{
bool ContainsData { get; }
void SetData(IRuleItem data);
IRuleItem GetData();
}
IItemDrawing和IRuleDrawing用于在BNFUPEditor工具的自定义ListBox中绘制规则。
IRuleItemClipboard是一个自定义实现,用于提供剪贴板服务。
RuleTable类中的编辑服务
使用此构造函数,您可以从包含语言定义BNF规则的TextReader构建一个RuleTable对象。
public RuleTable(TextReader rdr, ICompilableObjectFactory factory)
有一个事件处理程序,您可以使用它来获取编译消息。
public delegate void CompilerEventHandler(string ev);
public event CompilerEventHandler OnCompilationFeedback = null;
以下是公共属性:
public bool CaseSensitive { get; set; }
public bool DebugMode { get; set; }
public ICompilableObjectFactory Factory { get; set; }
public RuleBase Root { get; }
public IEnumerable<Type> ItemTypes { get; }
public IEnumerable<IRuleItem> SimpleItems { get; }
public IEnumerable<RuleBase> Rules { get; }
CaseSensitive:可用于使语言区分大小写或不区分大小写。
DebugMode:用于禁用编译器的消息日志记录。在保存RuleTable对象之前,将此属性设置为false,以避免由于反馈事件而导致的序列化错误。
Factory:用于将编译器与您类库中的对象创建器链接。
Root:包含语言的根规则。
ItemTypes:枚举库中实现IRuleItem接口的所有类型。
SimpleItems:枚举语言中定义的所有原子项目,即Token和CharacterSet类型。
Rules:枚举语言中的所有规则。
提供编辑功能的公共方法如下:
public void AddRule(RuleBase rule)
public void RefreshRule(RuleBase newrule, RuleBase oldrule)
public void AddItem(IRuleItem item)
public void RemoveRule(RuleBase rule)
public void RemoveItem(ItemBase item)
public RuleBase GetRule(string name)
public IRuleItem GetItem(string name)
public void LinkItem(IRuleItem item)
public IRuleItem RestoreItem(IRuleItem item)
public ICompilableObject Build(TextReader rdr)
使用AddRule方法,您可以向语言定义添加新规则。
RefreshRule用于在粘贴项目时将其转换为存储在规则列表或原子对象列表中的规范版本。
AddItem可用于向语言添加新标记或字符集。
RemoveRule从RuleTable中删除规则及其在其余规则中的所有实例。
RemoveItem是前一个函数用于删除标记或字符集的版本。
GetRule可用于按名称获取规则。
GetItem是此函数用于标记和字符集的版本。
LinkItem是一个方法,用于将项目对象中的事件处理程序链接到编译器事件处理系统。
RestoreItem在从剪贴板粘贴项目时使用。该项目将被RuleTable类中的项目列表所存储的规范版本替换。
Build是您必须调用以开始源代码编译的方法。
这就是全部。希望任何人都能发现这个软件对他们自己的项目有用。
感谢阅读!!!