65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (14投票s)

2010 年 2 月 4 日

LGPL3

11分钟阅读

viewsIcon

60907

downloadIcon

1698

在本文中,我们将讨论实现条件语句、循环和块。

在 GitHub 上克隆

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 eRightParen,那么我们就去相应的函数并执行语句列表。

折叠
So here is the algorithm:

Token 循环开始

如果:token==eBlock

转到 Block 方法。

Token 循环结束

 

Block 方法开始

       如果:token 序列“eBlock  eLeftParen eCharVal eColon

  1. 将“Name:Token index”存储在一个 map 中。
  2. 读取所有字符,直到找到匹配的“)” 
否则如果:我们得到 token 序列“eBlock eLeftParen eCharVal eRightParen
  1.  保存要从块执行返回的位置的索引。
  2. 在 map 中搜索名称,如果找到,则获取代码索引处的索引。
  3. 执行代码块。(此代码块甚至可以包含更多 #B 调用)
  4. 返回到调用块的位置。

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 库等。每当我将使用它时,我都会提供足够的细节。我们将使用它们,因为我们必须使用行业强度的成熟助手库来使我们的任务更容易,并更多地了解真正的编译本质,而不是重新发明所有轮子。而是我们将只重新发明编译/解释的轮子。

© . All rights reserved.