如何使用 JavaScript 实现一门编程语言(1) : 前言

时间:2024-05-31 08:02:37

(点击上方公众号,可快速关注)


英文: Mihai Bazon   译文:阿安

annn.me/implement-a-programming-language/


这是一系列关于 如何实现编程语言 的教程。如果你曾经写过一个解释器或编译器,那么这里可能没有什么新东西。但是,如果您使用正则表达式来“解析” 任何看起来像编程语言的东西,那么请至少阅读解析部分。让我们写出更少的错误代码!


目标受众是普通的 JavaScript / NodeJS 程序员。


我们要学什么?


  • 什么是解析器,以及如何编写解析器。

  • 如何编写解释器。

  • 为什么它们很重要。

  • 编写一个编译器。

  • 如何将代码转换为延续传递样式。

  • 一些基本的优化技术。


在两者之间,我会争论为什么 Lisp 是一种优秀的编程语言。 但是,我们将要使用的语言不是 Lisp。它有一个更丰富的语法(每个人都知道的经典中缀符号),除宏之外,它的功能与 Scheme相当。


可悲的是,宏是 Lisp 的最终堡垒,其他语言无法克服(除非它们被称为 Lisp 方言)。


首先,让我们想想我们的要实现的编程语言应该是什么样子。


我们应该想清楚自己想要实现的目标。把语法的严格描述放在一起是一个好主意,但是我会在本教程中使语法更加简单,下面的示例就是我们要实现 “λ” 语言:


# this is a comment

 

println("Hello World!");

 

println(2 + 3 * 4);

 

# functions are introduced with `lambda` or `λ`

fib = lambda (n) if n < 2 then n else fib(n - 1) + fib(n - 2);

 

println(fib(15));

 

print-range = λ(a, b)             # `λ` is synonym to `lambda`

                if a <= b then {  # `then` here is optional as you can see below

                  print(a);

                  if a + 1 <= b {

                    print(", ");

                    print-range(a + 1, b);

                  } else println("");        # newline

                };

print-range(1, 5);


请注意,标识符名称可以包含负号字符(print-range)。这是个人品味的问题:我总是在操作符旁边放置空格,我不喜欢太多的 camelCaseNames,而且我觉得短划线比下划线更好。编写自己的语言的好处是,你可以随心所欲地做到这一点。:)


输出是:


Hello World!

14

610

1, 2, 3, 4, 5


该语言看起来有点像 JavaScript,但它不同。首先,没有声明,只有表达式。表达式返回一个值,可以用来代替任何其他表达式。分号需要在“序列”中分隔表达式。花括号{和}创建这样一个序列,它本身就是一个表达式。它的值是最后一个表达式返回的值。以下是一个有效的程序:


a = {

  fib(10);  # has no side-effects, but it's computed anyway

  fib(15)   # the last semicolon can be missing

};

print(a); # prints 610


函数被引入其中一个关键字 lambda 或 λ(它们是同义词)。在关键字之后,必须有一个(可能为空的)用逗号分隔的变量名列表(可能为空),就像在 JavaScript 中一样 —— 这些是参数名称。函数体是一个单一的表达式,但它可以是一个包裹在{ … }中的序列。


没有 return 语句 – 在函数返回最后一个表达式给出的返回值。


没有 var。要引入新变量,可以使用 JSer 称之为 “立即执行函数” 的方式。就像在 JavaScript 中一样,使用 a lambda,声明变量作为参数。变量具有函数作用域范围,函数是闭包。


甚至 if 本身就是一种表达。在 JavaScript 中,您可以使用三元运算符获得该效果:


a = foo() ? bar() : baz();           // JavaScript

a = if foo() then bar() else baz();  # λanguage


当分支以一个花括号开始时, then 关键字是可选,你可以在 print-range 上面看到,否则它是必需的。

当替代分支存在时,else 关键字是必须的。再次,then 的 else 的分支主题为一个单一的表达式,但也可以花括号 “{}” 包含多个通过 “;” 分隔的表达式。

当 else 分支不存在并且 if 条件结果为 false,if 表达式的结果是 false。因此,false 是一个关键词,它表示我们的语言中唯一的 falsy 值:


if foo() then print("OK");


当 foo() 的结果不是 false 是,这点程序将会打印 “OK”。还有一个用于表示”真”的关键字 true,不是所非 false(当使用 JavaScript中 === 运算符的时候)的值都被理解成 true (其中包括数字 0 和空字符串 “”)。


还要注意,使用括号包裹 if 条件没有意义。不过,虽然它们是多余的,你添加它们也没有错误。


整个程序被解析,就好像它被嵌入大括号中一样,因此,除了最后一个表达式,您需要在每个表达式后面放置一个分号。


好了,这是就我们的小 λ 语言。它不一定是很完善的的。它的语法看起来很可爱,但也有陷阱。它有很多缺失的功能,如对象或数组; 我们并不关注这些缺失,因为对我们的旅程并不重要。只要你掌握了这个教程的所有内容,你可以轻松实现这些缺失的功能。


在下一节中, 我们将为这个语言编写一个解析器 。



觉得本文对你有帮助?请分享给更多人

关注「前端大全」,提升前端技能

如何使用 JavaScript 实现一门编程语言(1) : 前言