MPS教程(五)把之前的语言嵌入Java

MPS教程(五)把之前的语言嵌入Java

原文

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

爱看不看

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

知乎有一哥们私聊找我,问我能不能用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(" ** 马赛克 ** ");
    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 " ** 马赛克 ** ")
    (println " This is C# code ")
    (println " Fuck Java ")
  }
}

模块化之前的Generator

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

导入依赖

首先我们需要导入依赖,在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("$[** 马赛克 **]");] 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("** 马赛克 **");]]
  }
}

也就是说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 " ** 马赛克 **** 马赛克 **** 马赛克 ** ")
    (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 " ** 马赛克 ** ")
    (println " This is C# code ")
    (println " Fuck Java ")
    (run-all|>
        (println " ** 马赛克 **** 马赛克 **** 马赛克 ** ")
        (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选手,请继续你的表演。

发布于 2017-04-22 02:29

文章被以下专栏收录