构建编程语言:第二部分(为 BrainLess 添加条件、循环和块)






4.75/5 (14投票s)
在本文中,我们将讨论实现条件语句、循环和块。
- 下载 BrainLess V0.2 - 13.26 KB
- 下载 BrainLess V0.3 - 14.83 KB
- 下载 BrainLess V0.4 - 17.54 KB
- 下载 BrainLess_Snail V0.5 - 17.65 KB
- <<上一篇:构建编程语言 – 第一部分(创建 BrainLess)
1. 引言
我将把第二部分分为第二部分-A 和第二部分-B。在本文中,我们将更多地讨论 BrainLess 语言编译器/解释器的实现。我们将对 BrainLess 虚拟机进行一些修改。我们还将更多地讨论编译器的词法分析器和语法分析器部分。基本上,我们在 BrainLess VM 中添加了一些特殊的槽。这些将在我们进行比较、函数调用等时使用。
在第二部分-B 中,我们将实现 BrainLess 中的函数。
2. 背景
要了解 BrainLess 的创建过程,您可以参考我第一篇文章。
3. 使用代码
3.1 文件
在此文章中,我附上了 BrainLess 编译器、解释器的第二个版本。
下面对代码进行了简要描述。这将帮助您深入了解代码并进行自己的修改。
A. Helper.cpp 和 Helper.h
这些文件包含并将包含有助于创建 BrainLess 的辅助函数。目前,它们包含 FileRead
类。
此类负责从输入脚本文件读取。
B. constNDefs.h
此文件包含将在整个代码中使用的常量和定义。
C. MainDriver.cpp
此文件包含程序的 int main()
。
D. Lexer.cpp 和 Lexer.H
这些文件包含词法分析器。
class TokenAttrib
Token 可以具有属性,例如字符组“123
”是单个数字,其
值为 123
。此值由此类保存。
class Lex
这是词法分析器类。此类将逐个字符地从源文件读取,然后
根据 enum enmTokens
组中的任何一个对其进行分类。例如: “-*”是两个字符,但它们形成一个单一的 token。此类将识别此 token 并返回。
enum enmTokens
这是包含所有 token 列表的 enum
。组织此 token 有多种方法。一种可以由词法分析器使用,另一种 enum
可以创建供语法分析器寻址。但我们将仅为词法分析器和语法分析器使用一种。在本篇文章介绍更多术语时,我们将对此进行更详细的讨论。
E. Interpreter.cpp 和 Interpreter.H
这是 BrainLess 语言的解释器或编译器。负责此项工作的类是 Interpreter 类。
编译/解释从入口点函数开始。
void Interpreter::interpreteScript()
下一个重要函数是 void Interpreter::Sentence()
。
基本上 interpreteScript()
是一个循环,用于获取下一个 token,然后调用 Sentence()
,后者根据 token 调用相应的函数,简单来说,这将为 BrainLess 中的语句调用实际的解释器。
F. TapeMachine.h
此文件包含模拟 BrainLess 虚拟机的类,或者您可以说这是虚拟机。
template< typename storageType, typename IO, ulong iTapeSize>
class TapeMachine
{
...
};
这里它被设计成模板,以支持即插即用架构。虚拟上,您可以认为虚拟机包含 2 个部分。
存储类型,它告诉 VM 中每个槽的内存类型。如果您想更改它,只需更改 storageType
就足够了。这很容易做到。
IO 是 VM 的 IO 接口。目前它只支持控制台。但如果需要,可以轻松地接口化不同的 IO 类。
G. VMInterface.h
之前讨论过的 IO class
是此文件中描述的 class IOInterface
的一个实例。目前,它支持控制台应用程序,但可以扩展以支持基于 UI 的 IO 接口。
H. TapeMachine.cpp
此文件已删除。所有内容都已添加到 TapeMachine.h 文件中。现在它是一个模板类。
3.2 编译
要编译代码,只需在 Visual Studio 2008 express edition 中打开 BrainLess.sln 文件并进行编译。它不依赖于任何其他库。在 Windows 以外的任何平台上编译也很容易。只需将所有文件放在一个文件夹中,然后编写一个小的 make-file 来编译和链接它们。需要一个现代 C++ 编译器。
3.3 示例
代码中提供了一个示例文件 m.bl。执行它。
4. 语法分析器和词法分析器
在这里我们将更详细地讨论词法分析器和语法分析器。我们将在此引入更多概念。
4.1 回顾
首先,让我们回顾一下我们在本文第一部分介绍的一些概念。
符号
这是一个原子实体,由一个字符表示,有时由一个保留字或关键字表示。
例如:+ , ; EOF。FileReader
类为我们读取所有原子实体。
字母表
“A
” 是符号/token 的集合。但它应该是非空的、有限的。
示例:英语语言的英文字母集,C++ 的字母集。
+ - / * [a-z,A-Z,0-9] { } ( )"space"
对于 BrainLess,我们有以下字符集
A = {a...z, A...Z, *, +, -, [, ], (, ), 0...9, !, >, <}
在这里,我们的集合“A
”中没有“空格
”、“EOF
”、“\0
”、“\t
”、“\b
”。我们也没有“{
”和“}
”。
短语/单词/字符串
这是在字母表集“A
”上定义的。我们从字母表集中选择符号,并使用某些规则将它们组合起来得到一个 string
。
例如:在 C
中,标识符名称不能以数字开头。这是一个规则。
Lex 类识别这些,并将它们返回给 Interpreter
类。
对我们 BrainLess 而言,“-*
”、“--
”等是我们的语言的“单词”。
语言
所有单词的集合构成一门语言。
基本上 Interpreter::Sentence()
为我们识别语言集中的所有内容。对于当前的 BrainLess 编译器,如果它没有包含在 Sentence()
中,那么它就不属于该语言。
语法
要从某个单词序列中获取含义,我们需要一些规则。这些规则由语法给出。这是一组规则,所有规则都需要遵循才能使某些东西有意义。我们将这组规则称为“G
”表示语法。
示例:对于 BrainLess,符号 eIF eLeftParen eEquals eRightParen eLeftSqrParen <任何其他有效的句子/单词序列> eRightSqrParen eFI。
这些 token 中的每一个都是有效的。但如果我们有这样的情况
eIF eFI eLeftParen eEquals eRightParen eLeftSqrParen <任何其他有效的句子/单词序列> eRightSqrParen
这不是一个有效的句子。它没有意义。
4.2 短语结构和词法结构
4.2.1 词法结构
为了构建语言的单词,我们遵循某些规则。这些规则结构被称为语言的词法结构。
示例:读取和存储一个字符由“>>
”给出。数字由 BrainLess 中相邻的 {0-9}
字符集中的任何字符序列给出。这些都是 BrainLess 的单词。但用英语描述它们有一些缺点,比如它很冗长,其次它会造成混淆。因此,为了清晰起见,我们可以使用一些形式化符号。
让我们看看如何匹配单词“>>
”。它基本上是一个 pattern
字符串,所以我们需要一些模式匹配来识别它。模式匹配的工具是正则表达式。正则表达式通过使用字母表“A
”中的符号来指定 string
可能采用的形式。但为了施加结构,它还使用了一些其他符号。这些符号称为元符号。正则表达式在编程语言翻译中具有实际意义,因为它们可用于指定 token 的结构。
其中一些是
- 连接 - 符号或字符串可以通过将它们写在一起,或者使用元符号 · (点) 来连接,如果需要更清晰的话。
- 交替 - 两个符号“
a
”和“b
”之间的选择通过用元符号 | (竖线) 分隔来指示。 - 重复 - 符号“
a
”后跟元符号*
(星号) 表示允许出现零次或多次“a
”的序列。符号“a
”后跟元符号 + 表示出现一次或多次。换句话说a+ = a.a*
。 - 分组 - 符号组可以被元符号 ( 和 ) (括号) 包围。
对我们来说,Lex 类为我们识别正则表达式。因此,“>>
”这个单词可以用正则表达式定义为
>.>
4.2.2 短语结构
语言的短语结构是单个 token 如何组合以获得语言有意义语句的方式。这基本上由语言的语法处理。这由 Interpreter 类识别。
5. BrainLess VM 的更改
BrainLess VM 已更改。下图显示了更改。

这里引入了一个新的寄存器,称为 m_iSpecialRegStart
。该寄存器将保存索引 60
。索引 60
将存储我们通过比较当前头部值和当前头部 + 1 值而获得的值。
// The token "\<>"
void Interpreter::compareWithNext()
{
if(m_tapeVirtualMachine.getCurrentHeadValue() <
m_tapeVirtualMachine.getNthPosValFrmHead(1))
{/*If m_iTapeStore[m_iHeadPos] < If m_iTapeStore[m_iHeadPos + 1]*/
m_tapeVirtualMachine.setFlagRegister(HEX_1);
}
else if(m_tapeVirtualMachine.getCurrentHeadValue() >
m_tapeVirtualMachine.getNthPosValFrmHead(1))
{/*If m_iTapeStore[m_iHeadPos] > If m_iTapeStore[m_iHeadPos + 1]*/
m_tapeVirtualMachine.setFlagRegister(HEX_2);
}
else
{/*If m_iTapeStore[m_iHeadPos] == If m_iTapeStore[m_iHeadPos + 1]*/
m_tapeVirtualMachine.setFlagRegister(HEX_3);
}
}
6. 为 BrainLess 添加块
6.1 什么是块?
计算基本上是一个控制抽象实体(程序)的过程。我们无法触摸或感觉程序。但又有什么关系呢!!它为我们工作,所以谁在乎。计算基本上是指示这个抽象实体完成某些工作。这个实体可以完成一些工作,或者可以操作另一个称为数据的抽象数量,以给我们一些指令。现在有一些动作或一些指令我们会一次又一次地执行。一遍又一遍地写指令会很不方便。如果指令中有错误,那么我们需要在每个地方更改它。为了使这些事情更方便,我们将创建另一个特殊的抽象实体。它将被称为块。块的定义如下
#B(<Block Name>:<Block Body>)
当解释器看到 #B
时,它将生成 token 符号 eBlock
。看到这个之后,解释器会进入 void Interpreter::Block()
。
解释器在看到 eBlock
token 时采取的操作取决于下一组 token。如果是这样,我们得到下一个 token,如果下一个 token 是“:
”,那么我们就知道这是一个函数定义。因此,直到遇到“)
”之前的所有内容都是需要执行的代码。
另一方面,如果 token 序列是 eBlock eLeftParen eCharVal eColon eRightPare
,那么我们就去相应的函数并执行语句列表。n

So here is the algorithm:
Token 循环开始
如果:token==
eBlock
转到 Block 方法。
Token 循环结束
Block 方法开始
如果:token 序列“eBlock eLeftParen eCharVal eColon
”
否则如果:我们得到 token 序列“
- 将“
Name:Token index
”存储在一个 map 中。- 读取所有字符,直到找到匹配的“
)
”eBlock eLeftParen eCharVal eRightParen
”
- 保存要从块执行返回的位置的索引。
- 在 map 中搜索名称,如果找到,则获取代码索引处的索引。
- 执行代码块。(此代码块甚至可以包含更多 #B 调用)
- 返回到调用块的位置。
Block 方法结束
一个块可以包含更多块、IF...FI
子句、DO...DONE
循环。
源代码包含适当的注释。
7. BrainLess Snail 程序
一个小的程序,用于在 BrainLess Snail 中计算 N 个数字的总和。
N 是作为用户输入获取的。
![5]
>>
\>
DO
*
++
<
\>
DONE
![3]
\=
DO
*
--
\>
<
+*
DONE
![5]
8. 结论
到目前为止,我们已经讨论了一些理论,并实现了一个相当多的编程语言。但这绝不是终点。下一系列将更侧重于函数的实现。但在那之后,我将再次转向理论部分,但理论本身对我来说非常无聊,所以我们也将同时不断地实现一些工具和助手。请提供您的宝贵评论。如果您有任何疑问,请告诉我。
BrainLess 中没有空格的值。这意味着 #B 与 # B 相同。IFFI 也是一个有效的构造。这里的词法分析器就是这样工作的。它寻找第一个匹配项。
历史
- BrainLess Snail V 0.5 - 修复了
DO
循环结构的错误 - BrainLess V 0.3 - 添加了条件语句和循环。添加了块,
- BrainLess V 0.4 - 它还有一个日志记录器,日志记录器将记录每个 Interpreter 函数所花费的时间。
- 添加了块:#B(块名称 : 块体) - 声明一个块
- #B(块名称) 调用一个块
- 添加了运算符 "\<", "\=", "\>", "\<>"
- 添加了
IF
(条件) 语句FI
语句 - 添加了
DO
语句DONE
- BrainLess VM - 添加了特殊槽用于保存特殊值,如比较结果。
我已将代码移至 Google Code。更多更新将在那里提供。
http://code.google.com/p/brainless-labs/下一篇
我们将改进编译。
从下次开始,我们将开始使用更成熟的 C++ 库,如 Boost 库等。每当我将使用它时,我都会提供足够的细节。我们将使用它们,因为我们必须使用行业强度的成熟助手库来使我们的任务更容易,并更多地了解真正的编译本质,而不是重新发明所有轮子。而是我们将只重新发明编译/解释的轮子。