## 如何使用 JavaScript 实现一门编程语言(1) —— 前言
这是一系列关于 [如何实现编程语言](http://annn.me/tag/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86) 的教程。如果你曾经写过一个解释器或编译器,那么这里可能没有什么新东西。但是,如果您使用正则表达式来“解析” 任何看起来像编程语言的东西,那么请至少阅读解析部分。让我们写出更少的错误代码!
目标受众是普通的 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 条件没有意义。不过,虽然它们是多余的,你添加它们也没有错误。
整个程序被解析,就好像它被嵌入大括号中一样,因此,除了最后一个表达式,您需要在每个表达式后面放置一个分号。
好了,这是就我们的小 λ 语言。它不一定是很完善的的。它的语法看起来很可爱,但也有陷阱。它有很多缺失的功能,如对象或数组; 我们并不关注这些缺失,因为对我们的旅程并不重要。只要你掌握了这个教程的所有内容,你可以轻松实现这些缺失的功能。
- Web 开发笔记
- 从输入 URL 到页面加载完成的过程中都发生了什么事情?
- 从浏览器接收url到开启网络请求线程
- 开启网络线程到发出一个完整的http请求
- 从服务器接收到请求到对应后台接收到请求
- 后台和前台的http交互
- http的缓存
- 解析页面流程
- HTML解析,构建DOM
- CSS解析,构建CSSOM
- 资源外链的下载
- CSS的可视化格式模型
- 包含块(Containing Block)
- 控制框(Controlling Box)
- BFC(Block Formatting Context)
- IFC(Inline Formatting Context)
- 其它
- JS引擎解析过程
- JS的解释阶段
- JS的预处理阶段
- JS的执行阶段
- 回收机制
- 参考资料
- JavaScript模块化编程
- AMD
- requireJS
- CommonJS
- UMD
- ES6模块
- 参考资料
- 使用 JavaScript 实现一门编程语言
- 如何使用 JavaScript 实现一门编程语言(1) —— 前言
- 如何使用 JavaScript 实现一门编程语言(2) —— 编写一个解析器
- 如何使用 JavaScript 实现一门编程语言(3) —— Input stream
- 如何使用 JavaScript 实现一门编程语言(4) —— Token stream
- 如何使用 JavaScript 实现一门编程语言(5) —— AST
- 如何使用 JavaScript 实现一门编程语言(6) —— Interpreter
- 完整代码
- 参考资料
- 前端布局概论
- 参考资料
- Windows 笔记
- 错误解决
- win10应用商店无法登录提示0x80070426解决方法
- 使用技巧
- 设置 Hyper-V 和 VMware 共存
- Powershell
- WSL