我写了一个编程语言 HeraScript

298 阅读3分钟

HeraScript 是什么

这是我疫情期间里用闲暇时间写出来的一个编程语言,编译到 JavaScript 执行。
旨在给 JavaScript 加上一些好玩的特性,比如支持 partial apply、user-defined-operator、operator as function 等特性。(语法定义上没有什么斟酌,我觉得好玩就加上🐶

Hera-Langgithub.com/HeraScript/…
Hera-IDE: github.com/HeraScript/…

一些语法的实现方式

上面提到,在 Hera 中,我们实现了一些好玩的语法,下面我们来看看这些语法都是什么以及如何实现的。

partial apply

语法片段可以在这里看到:herascript.github.io/index.html?…

在 JavaScript 中,我们要对一个函数进行 partial apply,那么要求这个函数必须先经过 curry,比如下面这样:

Hera 中根本不需要这么麻烦,我们可以直接对函数进行 partial apply,柯里化的事情 Hera 编译器帮你做了!

这段代码编译后的 JavaScript 代码为:

那这个功能是怎么去实现的呢?
我们第一个想到的方法就是:在每个 function 定义的时候,存储 (functionName, parameterCount) 这样一个表,这样下次 function apply 的时候我们就可以从这个表中取出对应的参数数量,如果当前 apply 的参数不足,就将不足的部分补成一个 closure,这样就可以做到柯里化。

但是这样还是不够的,因为 function 的定义是有作用域的,不同作用域的同名函数的参数可能是完全不一样的。所以我们不能简单的做一个 (name, count) 的表,而是要给他加上作用域的概念,要变成 Stack::(name, count)。
关于作用域我们用一个 stack 去实现就好了,parser 中有进入作用域的地方进行入栈,出作用域的时候出栈。比如我们知道 block_statement 会形成作用域,那么在 parse 到 { token 的时候 push scope,在 parse 到 } token 的时候 pop scope,在每个 stack element 中去建 (name, count) 的表就可以建立具有作用域加持的参数映射表,寻找参数表的时候按照当前作用域逐级向上找就可以了。

user-defined-operator

语法片段可以在这里看到:herascript.github.io/index.html?…

在 Hera 中,支持自定义 operator,比如

infixl 1 !! -> x !! y => x[y]

这样我们就可以通过 [1, 2, 3] !! 1 这样的方式去访问 access array

那么这个又是怎么实现的呢?

  1. 说到 operator,第一个想到的就是 operator 之间的优先级: 2 + 3 * 5 之所以是先算 3 * 5 是因为 * 的优先级比 + 要高,所以我们自定义 operator 的时候也需要处理优先级的问题。
    我们的处理方式是,对 op 建立一个数组,数组的先后顺序代表了优先级,这样我们在处理的时候就可以按照数组的顺序来 parse expression,这样优先级高的 operator 就永远会被先计算。具体 expression 的计算规则可以参考 Haskell 的 buildExpressionParser

  2. 处理完优先级之后,我们要处理的是如何把 Hera 的 expression 与 native expression 映射起来。这里我们把 Hera 的 expression 规则、operator、native expression 规则 三个数据存在一张表里:(he, op, ne),这样我们在 parse 到 operator apply 的时候,就可以从表中取出对应的规则做两次 parse,然后替换掉对应的 token 即可完成语法的转换。