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

2017/04/07 MPS

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

准备工作

  • 阅读并跟随上一篇博客完成之前的工作。
  • 学会用 Java 写 Hello World

本文主要内容

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

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

有哪些新概念

  • 没有新引入的概念

开始吧

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

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

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

对了,忘记介绍概念了。

先复习一下

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

  • 是 AST 的节点
  • 可以拥有别的节点(作为父节点)
  • 可选地成为一个根节点
  • 上一篇没有提到的是,它可以拥有别的 AST 节点的引用。可惜我们暂时不会用到这个特性

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

为什么不现在说完

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

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

Generator

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

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

  • MPS 的 AST 就是宏的参数
  • MPS 的 Generator 就是宏的 body
  • MPS 的 Generator 非常强大,不过这个教程里的例子暂时体现不出来

我们来编写最基本的部分,先写个 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 的伟大。晚安。


Search

    Post Directory



    如果觉得这篇文章给您带来了收获或者说它值得您这么做,您可以选择对我进行捐助。
    下面是微信支付哟


    本作品由
    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.
    知识共享许可协议