MPS 教程三:制作一个简易语言(中)

by

在上一篇教程中,我们已经体验了 MPS 创建语言的流程的一半。 接下来,我们将进行 Code Generation ,把之前的『语言』生成的代码导出为 Java 代码,并运行它。

准备工作

本文主要内容

你在看上篇教程的时候肯定以为是分成上下的,结果没想到出了个『中』,你一定很绝望吧。

其实不是这样的,我只是想把最初的三个概念分开说而已, MPS 很简单的。

有哪些新概念

开始吧

打开你的 MPS ,进入上次创建的工程。

打开左边的、昨天说的不会讲太多但是是今天的主菜的『Generator』, 并打开昨天我们自动生成的 map_PrintlnSet。

你可以看到,这是一个命名非常不规范的 Java 类——一个空的 Java 类。不过这是生成的代码,不用担心。

对了,忘记介绍概念了。

先复习一下

上篇教程讲了Concept 这个概念(这句话翻译成英语会很鬼畜,哈哈哈哈)——它:

还有一些别的没有 cover 到的点,我将会在后期列出。

为什么不现在说完

一下子甩一堆概念是一种耍流氓。狠狠地黑一波现在绝大多数国产书。

看我的教程,你放心。我和那些半灌水不一样,我可是空灌水!

Generator

MPS 通过你的在破界神编辑器里编辑的 AST ,生成目标代码并编译。这也是为什么我之前说『java 文件不是源码而是目标文件』的原因。

而这里的Generator,就是你进行代码生成的模板。可以理解吧? MPS 按照这个模板来生成代码。 熟悉具有宏特性语言( i.e. Lisp 系列, Rust, Scala, etc.)的人应该可以很容易理解:

我们来编写最基本的部分,先写个 main 函数,里面写个输出语句。 请记得使用Live Template哦。

(上面的链接指向的博客是我还不知道 Live Template 可以定制的时候写的,所以没往深处讲,不过内容是正确的)

可以看到我已经写好了一个非常友善的输出语句。现在,我们要对它进行模板化处理——先选中这个输出语句。

注意

这是一个非常容易出错的地方,是绝大多数新人可能犯错的地方。在 Java 中,

System.out.println("")

是一个表达式,而

System.out.println("");

是一个语句

区别(仅仅是这一个地方的区别,他是正确的,但是不具有普适性,读者也不需要了解普适性的区别,因为用不到):

概念 语法上 对应的语义上
表达式 没分号 有『返回值』
语句 有分号 没有『返回值』,或者说返回 Unit

然后我们这里是选中一个语句!对它使用Alt+Enter,选择LOOP 开头 clause 结尾的那个。

然后你看到了原本不可能出现在 Java 代码中的东西。这个东西原本是需要一个很长的过程创建的, MPS 提供了快速创建的方法。

然后我们选中那个字符串的内容,不包含双引号,使用Alt+Enter,选择 content 结尾的那个:

然后你又一次看到了那个美元符号+中括号组成的东西。

然后我们的工作就完成了!是不是很懵逼!什么都没懂!没关系!现在先完成语言,等会再解释。

再次注意

千万不要忘记编译!!!Ctrl+F9!!!

我在写教程的时候就忘了, Orz。

回到刚才的语言

回到昨天创建的那个 PrintlnSet ,我们在打开的编辑器里面右键,选择Preview Generated Text

生成的代码是这样的:

package VerboseLang.sandbox;

/*Generated by MPS */


public class map_PrintlnSet {
  public static void main(String[] args) {
    System.out.println("Fuck you ZhiHu Editor!!!!");
    System.out.println("My name is Van, I'm an artist.");
    System.out.println("I'm a performance artist.");
  }
}

但是你似乎并没有看到类似『Run』、『Execute』之类的字样,对吧?因为我们需要让它成为一个可执行的 AST ,才能运行它。

而不仅仅是需要有 main 函数。

我们对左边 logical view 的 VerboseLang 那个地方进行Alt+Enter,找到上篇博客提到过的『Dependency』,再添加这个叫

jetbrains.mps.execution.util

的 Dependency :

然后找到 PrintlnSet 的定义,让它实现一个接口——IMainClass

注意, MPS 的接口和 Java 的接口也有一点不一样——

语言 inheritance
Java 无, Java8 以后可以使用 default
MPS

因此我们只要实现一个接口,也能获得它的功能。

实现了 IMainClass 的 AST 节点,在作根节点时,这颗 AST 就是可以运行的。

编译一下。

运行吧

还记得上次说的添加 JDK 这个 Dependency 吗?我有一处疏忽,别忘了给 Sandbox 也加上 JDK ,这样它才能调用 Java 类库:

右键你刚 Preview 了 Generated Text 的 PrintlnSet ,可以看到, Run 的按钮出现了!

点一下,然后就可以看到运行结果了!

好累呀,终于整出了可以运行的语言呀~

这 Generator 里面的东西到底是啥

前文说过, Generator 实际上就是宏,它是代码模板, MPS 根据你写的 AST ,对 Generator 里面的代码进行一次 map。

当它 map 到一个有 macro 的节点时(也就是刚才我们整进去的美元符号和方括号一家亲的东西), 会根据这个 macro 的要求,将你在 Sandbox 里面写的 AST 的信息填进去。

而原本写在美元符号+方括号一家亲里面的东西,就只是一个占位用的东西,多数情况下,多数内容都会被替换掉。

回顾一下刚才那个 AST ,我们对一个『语句』加上了一个循环的 macro ,也就是所它会对所有的循环内容进行遍历, 然后对里面的内容分别进行填充,每个循环内容产生一份这个东西,然后对里面的 macro 带入这个循环内容进行处理。

而我们对字符串加上的那个 macro ,就是将那个『循环内容』,也就是那个 Println ,把它的 content 填进去,原本的就没了。

比如我们这个例子, MPS 对很多个『Println』进行循环,那所有的 Println 就会被遍历,每个 Println 对应一个这个语句, 然后它们的 content 就会被依次填入字符串。

也就有了你看到的『Generated Text』的样子。

预告

不用你说我都知道,这语法:

println set { 

  clauses : 
    println { 
      content : Fuck you ZhiHu Editor!!!! 

    } 
    println { 
      content : My name is Van, I'm an artist. 

    } 
    println { 
      content : I'm a performance artist. 

    } 
}

丑死了!!!!!!!!!!!!!!!!

下期讲怎么改变它的语法。

不用担心重构代码的问题,由于存储在 MPS 里的是 AST ,因此,你改变了语法之后,原本的代码也会随着语法的改变而改变。

这得益于 LOP 的伟大。晚安。


Tweet this
Top


创建一个 issue 以申请评论
Create an issue to apply for commentary


协议/License

本作品 MPS 教程三:制作一个简易语言(中) 采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可,基于 http://ice1000.org/2017/04/07/MakeSimpleLangWithMPS/ 上的作品创作。
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
知识共享许可协议