突然 MPS 教程(五),扩展 Java

2017/04/20 MPS

话说我一开始时就是抱着肯定没人看的心情写的这个系列, 不过写了之后居然还是有不少人向我反馈表示在看,我还是很开心的。

知乎有一哥们私聊找我,问我能不能用 MPS 扩展其他语言。答案当然是可以的,不过我的教程里面似乎还没有, 所以我就临时发布了这篇教程。

众所周知,在 MPS 中,有一个语法和 Java 一样的内置语言叫做BaseLanguage,一般情况下,我们编写代码, 让 MPS 把 AST 给 map 成BaseLanguage代码,然后编译为文本形式的 Java 代码。

这次说什么

在这篇教程中,我们将模块化我们的 Generator ,并使我们能在BaseLanguage当中写我们的语言的代码。

如果你做了上一篇博客的作业 的话,你就可以体验一把在 Java 里面写 C#的感觉了。虽然这么有点不好(估计会引发一场巨大巨大的战争), 但是你看懂了这篇教程并跟着来了一波之后,你就已经可以任意地扩展BaseLanguage的语法了。

我在写博客之前先验证了一下自己的想法,最终出来的代码是这样的:

public class Ice1000 {
  public static void main(string[] args) {
    Console.WriteLine(" Fuck Zhihu Editor ");
    Console.WriteLine(" This is C# code ");
    Console.WriteLine(" Fuck Java ");
  }
}

再次提醒, MPS 的 LOP 中,语言的概念早已被弱化,你编辑的不是代码,是 AST。

然后我刚写到这的时候我的朋友Glavo说混合风格看着吐血。

于是为了再恶心他一把,我把语法改成了 Lisp 风格。见下代码:

public class Ice1000 {
  public static void main(string[] args) {
    (println " Fuck Zhihu Editor ")
    (println " This is C# code ")
    (println " Fuck Java ")
  }
}

模块化之前的 Generator

我们回顾一下,上次我们整了个 Generator ,它直接在一个map_PrintlnSet里面对PrintlnSet所有的Println 进行一个 map 操作,把它转化为 Java 代码。这里我们应该先把Println的 CodeGen 做成一个模块,让它可以被用于BaseLanguagePrintlnSet两种语言。

导入依赖

首先我们需要导入依赖,在VerboseLang中的Dependency选绿色加号,找到BaseLanguage,选择, 并像这样把Scope选成Extend,代表这门语言扩展了BaseLanguage

然后我们让Println继承Statement,而不是之前的BaseConcept

concept Println extends Statement
                implements <none>

instance can be root: false
alias: p
short description: <no short description>

properties:
content : string

children:
<< ... >>

references:
<< ... >>

新建一个子 Generator

然后我们跑到 Generator 那里,创建一个reduction rule, Concept 选Println

然后在右边的那个红光满面的地方Alt+Enter,选择新建一个模板。

然后在模板里面新建一个Statement。注意,这里的模板都是BaseLanguage的模板。

因此,你可以直接在里面写 Java。比如,输入sout,出来一个System.out.println();

然后选中整块代码,加上一个Template Fragment,表示这部分是一块模板(你可以只选中一部分作为模板,剩下的部分用于满足静态分析, 保证语法正确而已):

最后我们把这段代码补全成我们之前写的那样(就是在字符串那个位置加一个 对 node 的 content 进行 map 的 macro,和我之前在讲 Generator 的时候操作一致):

完了应该是这样:

<TF [System.out.println("$[Fuck zhihu]");] TF>

使用这个子 Generator

然后我们转到map_PrintlnSet,让它调用这个Template Fragment

首先把上次写的都删了,重新整个LOOP MACRO

然后把再选中里面的整个语句,给它加上一个宏,然后看到左边俩红色美元符号中间报错:

我们在这里选择名叫COPY_SRC的宏,表示直接把源码抄过来。完了应该是这样的:

public class map_PrintlnSet {
  public static void main(string[] args) {
    $LOOP$[$COPY_SRC$[System.out.println("Fuck Zhihu");]]
  }
}

也就是说 MPS 还有更高级的抄源码的方式,不过本文不会说的。因为懒。

现在编译一下语言,回到我们之前写的那个粗鄙的Sandbox,右键Preview Generated Text,应该是没问题的, 运行也应该没问题。如果有问题,请点击菜单栏的Build -> Rebuild Project

写一些 BaseLanguage

现在我们在我们的Sandbox里面导入BaseLanguage,然后 rebuild 一下,这样我们就可以在这个Sandbox里面写 BaseLanguage的代码了:

然后新建一个class

无论如何你都应该会写这些东西了:

public class BaseLanguageClassUsedForTesting {
  public static void main(string[] args) {
    <no statements>
  }
}

在 BaseLanguage 里面使用我们刚才定义的东西

注意,我们之前写Println这个 Concept 的时候,曾经为它起过一个 alias 叫p。 我们要记住它,然后在<no statement>处,Alt+Enter,输入这个 alias (我是p所以就输了p):

然后你就惊喜地看到了我们之前写的 Editor 的东西出现在了这里!

public class BaseLanguageClassUsedForTesting {
  public static void main(string[] args) {
    (println " <no content> ")
  }
}

随便写点什么玩玩吧。你现在已经成功地扩展 Java 了。

作业

这次的作业留一个比较难的,

扩展 BaseLanguage ,把整个 PrintlnSet 也塞进去。

要求:

  1. 在 PrintlnSet 的 Generator 中调用 Println 的
  2. 把 PrintlnSet 原本的 root Generator 换成对 PrintlnSet 的 Generator 的调用

也就是说确保所有代码都只写了一次,复用所有能复用的模块。

也就是说你要做成这样(我自己也实验了一下,是可以的,我把语法改成了我最近写的比较多的 Lisp 风格(最近比较沉迷 Lisp 啊)):

  • Sandbox1:
(run-all|> 
    (println " Fuck you ZhiHu Editor!!!! ")
    (println " My name is Van, I'm an artist. ")
    (println " I'm a performance artist. ")
)

这个不算难,类似的事情我们上次已经做过了。

  • Sandbox2:
public class Ice1000 {
  public static void main(string[] args) {
    (println " Fuck Zhihu Editor ")
    (println " This is C# code ")
    (println " Fuck Java ")
    (run-all|>
        (println " Zhihu's Editor is anti-human ")
        (println " MPS is a good IDE ")
        (println " Language-Oriented Prorgamming is good! ")
    )
  } 
}

这个比较骚,难度也比较大。我决定上传我自己做的版本, 你们可以做完作业之后对答案。至于能不能成功导入就看你的运气了。

这是我做的那个版本在 MPS 里面的样子:

再说说 MPS 的好处

今天又有人问我 MPS 解决了什么问题,然后我又听到了这样的言论:

啥也没解决,就是给你了个非文本 parser 的 parser generator。

好吧我就随口说说,反正你们都觉得是垃圾。

  • 不会编程的人不知道编程时很多细节,导致 UI 和程序员互肝
  • 文本编辑器还得 Parse 你的代码,实现不一样的 Parser 可能结果不一样(参考 IntelliJ Scala )太烂
  • Parser 处理不了有歧义的语法, MPS 可以写出有歧义的代码
  • MPS 可以在代码里面画图表,画控件,文本的代码不行
  • 一门语言的语法固定了,除非操编译器,否则不能加语言特性, MPS 可以

然后我说,

要不是写教程,我做刚才那个东西只需要两分钟

然后对方说,

我写个 yacc file……哦,写个 bnf 两分钟都不到。

这个故事未完待续,你们可以围观我们俩傻逼撕

更新

撕逼道具做好了,你们可以下载了看,我就不信他能做出这个

下载之后改下 Demo ,我写丑了,但是语言没问题。

预览效果,你也可以把下载的 Demo 改成这样:

运行结果:

更新 2

最后我图个乐呵,把它弄成这样了:

  1. 字符串必须含 Fuck 子串不然报错
  2. 带图片

yacc 选手,请继续你的表演。


Search

    Post Directory



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


    本作品由
    突然 MPS 教程(五),扩展 Java采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。
    基于 http://ice1000.org/2017/04/20/ExtendBaseLanguage/上的作品创作。
    This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
    知识共享许可协议