
引言
问候所有来我这里的人,阅读我的下一篇文章。
我再说一遍,我描述了基于先前工作的一种编程语言语言的创建,其结果
将在本文中
描述 。
在第一部分(链接:
habr.com/post/435202 )中,我描述了设计和编写语言VM的阶段,该语言将以我们将来的语言执行我们将来的应用程序。
在本文中,我计划描述创建中间编程语言的主要阶段,该语言将被组装为抽象字节码,以便直接在我们的VM上执行。
我认为立即提供指向项目网站及其存储库的链接不会有什么坏处。
网站资料库我必须马上说所有代码都是用FPC编写的,我将举一些例子。
因此,我们开始启蒙。
我们为什么要放弃中间语言?
值得理解的是,将程序从高级语言直接转换为可执行的字节码(由一组有限的指令组成)非常简单,因此最好通过在项目中添加中间语言来将其简化一个数量级。 逐步简化代码比立即用一组操作码呈现数学表达式,结构和类要好得多。 顺便说一下,这就是大多数第三方翻译器和编译器的工作方式。
在上一篇文章中,我写了关于如何实现语言VM的文章。 现在,我们需要为其实现一种类似于汇编程序的语言,并为进一步编写翻译程序提供功能。 在这些阶段,我们为将来的项目奠定了基础。 值得理解的是,基础越好,建筑物越陡峭。
我们迈出第一步来实现这个奇迹
首先,值得设定一个目标。 我们实际上会写什么? 最终代码应具有什么特征,它应该做什么?
我可以创建项目的这一部分应该包括的主要功能部分的列表:
- 简单的汇编程序。 将简单的指令转换为VM的一组操作码。
- 用于实现变量的功能的基本实现。
- 使用常量的功能的基本实现。
- 在翻译阶段支持方法的入口点并计算其地址的功能。
- 也许还有更多功能性的面包。
上面的插图显示了一种中间语言的代码片段,该片段将由原始翻译器转换为VM的代码,将对此进行讨论。
因此,设定了目标,让我们继续执行。
编写一个简单的汇编器
我们问自己是什么汇编程序?
实际上,这是一个执行操作码而不是文本描述的程序。
考虑以下代码:
push 0 push 1 add peek 2 pop
处理完汇编代码后,我们获得了VM的可执行代码。
我们看到指令可以是单音节和双音节。 没有更多有关堆叠VM的复杂说明。
我们需要一个可以从字符串中提取标记的代码(考虑到其中可能包含字符串)。
我们写它:
function Tk(s: string; w: word): string; begin Result := ''; while (length(s) > 0) and (w > 0) do begin if s[1] = '"' then begin Delete(s, 1, 1); Result := copy(s, 1, pos('"', s) - 1); Delete(s, 1, pos('"', s)); s := trim(s); end else if Pos(' ', s) > 0 then begin Result := copy(s, 1, pos(' ', s) - 1); Delete(s, 1, pos(' ', s)); s := trim(s); end else begin Result := s; s := ''; end; Dec(w); end; end;
好的,现在我们需要为每个语句实现类似switch-case的构造,并且我们的简单汇编器已准备就绪。
变数
回想一下,我们的VM具有一组指针来支持变量以及相应的静态寻址。 这意味着使用变量的功能可以表示为TStringList,其中字符串是变量的名称,而索引是其静态地址。 应当理解,此列表中变量名的重复是不可接受的。 我认为您可以想象必要的代码,甚至可以自己编写。
如果您想看一下完成的实现,那么欢迎您:/lang/u_variables.pas
常数
这里的原理与变量相同,但是只有一件事。 为了进行优化,最好不要绑定常量名称,而是绑定它们的值。 即 每个常数值可以具有一个TStringList,该字符串将用于存储具有该值的常数的名称。
对于常量,您应该指定数据类型,因此,为了将其添加到语言中,您将必须编写一个小型解析器。
实施:/lang/u_consts.pas
方法入口点
实现代码阻塞,支持不同的设计等。 对此功能的支持应在汇编程序级别实现。
考虑一个代码示例:
Summ: peek 0 pop peek 1 pop push 0 new peek 2 mov push 2 push 0 add jr
上面是Summ方法的示例翻译:
func Summ(a, b): return a + b end
应该理解,没有用于入口点的操作码。 Summ方法的切入点是什么? 此素数是下一个操作码入口点的偏移量。 (操作码的偏移量是相对于可执行抽象字节码开头的操作码编号)。 现在我们有一个任务-我们需要在编译阶段计算此偏移量,并且可以选择将Summ常量声明为该数字。
为此,我们为每个操作员编写一个权重计数器。 我们有简单的单音节运算符,例如“ pop”。 它们占用1个字节。 还有更复杂的代码,例如“ push 123”(推123)-它们占用5个字节,操作码1个字节,无符号int类型4个字节。
添加对入口点汇编程序的支持的代码的实质:
- 我们有一个计数器,假设我= 0。
- 我们遍历代码,如果我们具有“ push 123”类型的构造,则向其添加5(如果简单操作码为1)。如果有入口点,则将其从代码中删除,并使用计数器值和入口点的名称声明相应的常量。
其他功能
例如,这是处理之前的简单代码转换。
总结
我们已经实现了小型汇编程序。 我们将需要它来实现基于它的更复杂的转换器。 现在,我们可以为我们的VM编写小型程序。 因此,在其他文章中,将描述编写更复杂的翻译器的过程。
多谢您读完为止。
如果您不清楚某些事情,那么我正在等待您的评论。