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也塞进去。
要求:
- 在PrintlnSet的Generator中调用Println的
- 把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
最后我图个乐呵,把它弄成这样了:
- 字符串必须含Fuck子串不然报错
- 带图片
yacc选手,请继续你的表演。