Bird 编程语言:第三部分






4.88/5 (5投票s)
一种新的通用编程语言,目标是快速、高级且易于使用。
关于 Bird 的文章
- Bird 编程语言:第一部分
- Bird 编程语言:第二部分
- Bird 编程语言:第三部分
目录
Bird 结构概述
- 表达式由不同的识别器进行解析,节点传递给插件。
- 插件可以修改识别出的表达式,包括类型管理、常量计算
- 架构用于为特定平台生成代码。目前只有 x86。
代码解析基础
该语言由识别器组成,通过替换识别器即可支持新语言。函数、类型、变量将在 Bird 和新语言之间共享。我没有使用解析器生成器,因为我认为使用其他软件可能会阻止我实现某些功能,或者我不得不以不同的方式做事。
CompilerState
类包含一个 Language
对象,其中包含所有识别器。这是 Language
类
public abstract class Language : LanguageNode
{
public IList<IExprRecognizer> ExprRecognizers;
public IList<IIdRecognizer> IdRecognizers;
public IList<ICommRecognizer> CommRecognizers;
public IList<IModRecognizer> ModRecognizers;
public IList<INameGenerator> NameGenerators;
public IList<IResultSkippingHandler> GlobalHandlers;
public IArgRecognizer ArgRecognizer;
public IGenericRecognizer GenericRecognizer;
public IGroupRecognizer GroupRecognizer;
public IRetValLessRecognizer RetValLessRecognizer;
public IInnerScopeRecognizer InnerScopeRecognizer;
public IVarDeclRecognizer VarDeclRecognizer;
public ITypeDeclRecognizer TypeDeclRecognizer;
public IConstDeclRecognizer ConstDeclRecognizer;
public INamespaceDeclRecognizer NamespaceDeclRecognizer;
public IDeclarationRecognizer DeclarationRecognizer;
public IGlobalScopeProcessor GlobalScopeProcessor;
public ICodeFileProcessor CodeFileProcessor;
public ICodeProcessor CodeProcessor;
...
}
字符串处理
Bird 使用 StringSlice
来存储 string
,以避免在调用 string.Substring
时进行新的 string
分配。此结构还包含括号处理和其他操作的函数。CodeString
结构由一个 StringSlice
和对 CodeFile
类的引用组成,该类还可以管理它们的行和缩进。CodeString
有一个 Line
字段,当需要其子字符串时,它会更新 Line
成员。CodeString
的大多数方法只是 StringSlice
函数的包装器。
ISkippingHandler
接口用于跳过 string
常量或括号中的结果。识别器可以在 ResultSkippingManager.Datas
中存储数据,如果需要的话。在某些情况下,必须确保括号不被跳过。如果 ResultSkippingManager.DoNotSkipBrackets
为 true
,则括号识别器不应跳过括号。
public class BracketRecognizer : LanguageNode, IExprRecognizer,
IIdRecognizer, IResultSkippingHandler
{
...
public int SkipResult(ref ResultSkippingManager RSM)
{
if (!RSM.DoNotSkipBrackets && Helper.GetBracket(RSM.CurrentChar, RSM.Back))
{
var Handlers = RSM.SkippingHandlers;
if (RSM.Back)
{
var NewString = RSM.String.Substring(0, RSM.Current + 1);
var Pos = NewString.GetBracketPos(true, Handlers);
if (Pos != -1) return Pos;
}
else
{
var NewString = RSM.String.Substring(RSM.Current);
var Pos = NewString.GetBracketPos(false, Handlers);
if (Pos != -1) return Pos + RSM.Current;
}
}
return -1;
}
}
这是 BracketRecognizer
类。它必须返回一个字符串搜索应继续的索引,或者返回 -1
。GetBracketPos
方法返回字符串以其结尾或开头匹配的括号的位置。如果第一个参数为 false
,则字符串必须以朝向右方的括号开头。
LanguageNode 类和识别器
LanguageNode
类包含一个名为 Operators
的字符串数组,它会识别这些字符串,以及一个名为 LanguageNode.Skip
的字符串数组,它应该跳过这些字符串,因为要识别的运算符是它们的一部分。例如,一个加法识别器有 +、- 运算符,那么跳过数组应该包含 ++、--。NewLine
字符串包含哪些运算符需要前一行或后一行。例如,如果一行以 + 结尾,则会继续到下一行
public abstract class LanguageNode
{
public DataList Data = new DataList();
public Language Root;
public LanguageNode Parent;
public IList<LanguageNode> Children;
public IList<IResultSkippingHandler> SkippingHandlers;
public string[] Operators;
public string[] SkipFind;
public string[] Skip;
public string[] NewLineLeft;
public string[] NewLineRight;
public string[] OnlyLeft;
public string[] OnlyRight;
...
}
识别以递归方式完成。我认为这是一种简单但可能不是解析代码文件的最快方式。为了减少在 string
操作上花费的时间,我使用了自己优化的函数,它们速度快得多,尽管代码生成花费的时间要多得多。目前我的编译器可以在大约 1 秒内处理 10,000 行代码。
表达式识别器应继承自此类和 IExprRecognizer
接口。识别器可以使用 Find2
函数来确定哪些运算符不是一元运算符。如果结果的左侧以运算符结尾或为空,则会忽略该结果。当仅使用 LanguageNode.Operators
不够时,例如转换识别器需要实现 IFind2Handler
接口。以下代码是加法识别器
public class AdditiveRecognizer : LanguageNode, IExprRecognizer
{
public AdditiveRecognizer()
{
Operators = new string[] { "+", "-" };
NewLineLeft = NewLineRight = Operators;
}
public ExprRecognizerRes Recognize
(CodeString Code, ExprPlugin Plugin, ref ExpressionNode Ret)
{
var Result = ExprRecognizerHelper.Find2(this, Plugin.Container, Code.String);
if (Result.Position != -1)
{
var Ch = ExprRecognizerHelper.TwoParamOpNode(Code, Result, Plugin);
if (Ch == null) return ExprRecognizerRes.Failed;
Operator Op;
if (Result.Index == 0) Op = Operator.Add;
else if (Result.Index == 1) Op = Operator.Subract;
else throw new ApplicationException();
Ret = new OpExpressionNode(Op, Ch, Code);
return ExprRecognizerRes.Succeeded;
}
return ExprRecognizerRes.Unknown;
}
}
这仍然与 NumberRecognizer
不兼容,因为浮点数可以带有 e 表示法(2e+2 = 2 * 102)。为了解决这个问题,NumberRecognizer
向 LanguageNode.SkippingHandlers
添加了一个结果跳过器。ExprRecognizerHelper.Find2
首先接受一个 LanguageNode
参数,该参数用于从中获取跳过处理器。
代码生成器
这是 Bird 中最复杂的部分。它占整个项目的约三分之一。以下是它编译函数的主要步骤
- 处理表达式
- 计算表达式结果的存储位置
- 计算局部变量的位置
- 检查是否需要临时寄存器(在 x86 汇编中,大多数指令只能有一个内存操作数,有些必须是内存位置或通用寄存器,等等)
- 如果需要临时寄存器,则重新开始数据位置分配,分配临时寄存器,并继续到第 2 点。
- 生成汇编(不含跳转)
- 跳转优化
- 跳转替换
跳转优化与替换
int Fib(int x)
if x < 2: return 1
else return Fib(x - 1) + Fib(x - 2)
在没有跳转优化的此函数将具有以下汇编
_Fib:
push ebx
push ebp
mov ebp, eax
cmp ebp, 2
jge _26
mov eax, 1
jmp _2
jmp _24
_26:
mov eax, ebp
dec eax
call _Fib
mov ebx, eax
mov eax, ebp
sub eax, 2
call _Fib
add eax, ebx
jmp _2
_24:
_2:
pop ebp
pop ebx
ret
标签 _26
和 _24
用于条件,标签 _2
用于函数返回。有一些不必要的跳转和标签应该被删除
_Fib:
push ebx
push ebp
mov ebp, eax
cmp ebp, 2
jge _26
mov eax, 1
jmp _2
_26:
mov eax, ebp
dec eax
call _Fib
mov ebx, eax
mov eax, ebp
sub eax, 2
call _Fib
add eax, ebx
_2:
pop ebp
pop ebx
ret
如果跳转目标与其执行的操作相同,则可以节省一次跳转。除非目标太长,否则会替换该跳转
_Fib:
push ebx
push ebp
mov ebp, eax
cmp ebp, 2
jge _26
mov eax, 1
pop ebp
pop ebx
ret
_26:
mov eax, ebp
dec eax
call _Fib
mov ebx, eax
mov eax, ebp
sub eax, 2
call _Fib
add eax, ebx
pop ebp
pop ebx
ret
临时寄存器处理
IsEqual
函数需要 3 个临时寄存器来实现 mov
指令只能有一个内存操作数,并且内存位置不能有内存地址
long* g_Pointers
int g_Index
long g_Value
bool IsEqual()
return g_Value == g_Pointers[g_Index]
_IsEqual:
mov eax, dword[_g_Pointers]
mov edx, dword[_g_Index]
mov ecx, dword[eax + edx * 8]
cmp dword[_g_Value], ecx
jne _27
mov ecx, dword[eax + edx * 8 + 4]
cmp dword[_g_Value + 4], ecx
jne _27
mov al, 1
ret
_27:
xor al, al
ret
条件汇编生成
这是条件汇编生成的主要函数(Bird.CodeGenerator.GetConditionCode)
void GetConditionCode(ExpressionNode Node, int Then, int Else, bool ElseAfterCondition)
Then
和 Else
是标签的索引。如果 ElseAfterCondition
为
True
,则条件后跟着 else 块,跳转的条件与操作符相同,并跳转到Then
标签False
,则条件后跟着 then 块,跳转的条件被否定,并跳转到Else
标签
这是一个例子。
void WhileTest(bool Loop)
var a = 0
while Loop
a++
void DoWhileTest(bool Loop)
var a = 0
do
a++
while Loop
; This is the assembly without jump replacements
_WhileTest:
xor edx, edx
_90:
test eax, eax
jnz _18
inc edx
jmp _90
_18:
ret
_DoWhileTest:
xor edx, edx
_93:
inc edx
test eax, eax
jz _93
ret
; After jump replacements
_WhileTest:
xor edx, edx
test eax, eax
jnz _18
_89:
inc edx
test al, al
jnz _89
_18:
ret
_DoWhileTest:
xor edx, edx
_93:
inc edx
test eax, eax
jz _93
ret
在 WhileTest
函数中,条件后跟着 then 块,ElseAfterCondition
为 false
。在 DoWhileTest
中,它后面跟着 return 命令(当条件为 false
时发生),ElseAfterCondition
为 true
。
这是 and
和 or
运算符的工作方式
void While_OrTest(bool x, y, z)
var a = 0
while x or y or z
a++
void DoWhile_OrTest(bool x, y, z)
var a = 0
do
a++
while x or y or z
void While_AndTest(bool x, y, z)
var a = 0
while x and y and z
a++
void DoWhile_AndTest(bool x, y, z)
var a = 0
do
a++
while x and y and z
_While_OrTest:
xor ecx, ecx
_45:
test al, al
jnz _44
test ah, ah
jnz _44
test dl, dl
jz _7
_44:
inc ecx
jmp _45
_7:
ret
_DoWhile_OrTest:
xor ecx, ecx
_50:
inc ecx
test al, al
jnz _50
test ah, ah
jnz _50
test dl, dl
jnz _50
ret
_While_AndTest:
xor ecx, ecx
_57:
test al, al
jz _9
test ah, ah
jz _9
test dl, dl
jz _9
inc ecx
jmp _57
_9:
ret
_DoWhile_AndTest:
xor ecx, ecx
_62:
inc ecx
test al, al
jz _10
test ah, ah
jz _10
test dl, dl
jnz _62
_10:
ret
在所有 while
和 do
-while
测试中,最后一个条件保持不变,ElseAfterCondition
仅在前两个条件中更改。对于 and
条件,ElseAfterCondition
为 false
,对于 or
条件,它为 true
。
这是一个包含这两个运算符的示例
void While_AndOrTest(bool x, y, z, w)
var a = 0
while (x or y) and (z or w)
a++
void DoWhile_AndOrTest(bool x, y, z, w)
var a = 0
do
a++
while (x or y) and (z or w)
_While_AndOrTest:
xor ecx, ecx
_71:
test al, al
jnz _72
test ah, ah
jz _11
_72:
test dl, dl
jnz _70
test dh, dh
jz _11
_70:
inc ecx
jmp _71
_11:
ret
_DoWhile_AndOrTest:
xor ecx, ecx
_77:
inc ecx
test al, al
jnz _79
test ah, ah
jz _12
_79:
test dl, dl
jnz _77
test dh, dh
jnz _77
_12:
ret