在上一篇面向语言编程LOP(Language Oriented Programming)方法介绍中介绍了LOP以及一种LOP的实现方法MPS,本篇主要讲解一下使用MPS1.5的一个示例,以便大家能更好的理解是如何使用MPS实现LOP项目的。

需求

计算一个Java/PHP开发人员的收入,输入在Java和PHP项目投入的时间,计算器自动计算收入值,界面表示如下:

目标

通过写以下4行代码来生成这个计算器(10和5是常量)

 

view source print ?
1 calculator MySalary
2 input PHP Hours
3 input Java Hours
4 output Java Hours*10 + PHP Hours*5

 

主要步骤

  • 生成前面目标中提到的语法的一种语言来实现计算器。语言定义基本的逻辑概念、组成规范、关系以及各自的行为
  • 生成一个生成器,定义构建swing应用程序的规则
  • 使用我们创建的语言来实现一个计算器
  看起来完成这个示例使用现有语言会更快些,但是这里我们的意图是想展示是当你如何使用MPS来建立一种特定功能的语言。

下面主要讲解一下语言定义部分,代码生成器部分请参考MPS1.5的示例

生成项目

生成一个新的项目,项目名为:calculator

按Next后,生成一种新的语言,设定语言命名空间为: jetbrains.mps.tutorial.calculator命名空间一般为companyName.meaningful.name)

再按Next,生成一种新的方案(方案是一套特定语言的模型):

理想情况下,语言和使用语言的方案是分离的两个项目,但是建议在生成语言时把这个项目放在一起,便于测试新的语言。

按Finish后进入项目视图,默认展开语言项目jetbrains.mps.tutorial.calculator()节点:

语言项目下有多个节点,这些节点前面都带有M图标,分别代表模型的不同方面:

  • structure:描述语言语法
  • editor:描述编写语言时的编辑器
  • constraints:描述一些约束
  • type system:描述如何计算节点类型

jetbrains.mps.tutorial.calculator.sandbox()表示我们的测试方案节点,Modules Pool() 包含一系列可以参考使用的语言和方案,后面会再介绍这些内容。

 

下面我们开始来建立Calculator语言

Calculator概念(concept)

  就像类定义它的实例字段、方法和其他成员的结构一样,概念定义它的实例属性、引用和子概念定义的结构。概念是可以继承的,缺省每一个概念都从 BaseConcept继承下来,它将会继承父概念的所有属性、子概念和引用等结构。

  首先我们需要生成一个新的概念:Calculator。设置instance can be roottrue,实现INamedConcepts接口以便具有Name属性,

 

提示:Tab/Shift+Tab可以导航编辑焦点<> ;按Ctrl+Space可以打开自动完成提示列表

生成一个编辑器

  MPS编辑器看上去是文本编辑器,但是它是一个直接使用语法树的一个结构化编辑器。编辑器使用单元格(cells)来表示概念节点,这里有以下几种节点类型:

  • 属性单元格: 编辑节点属性
  • 常量单元格:显示常量值
  • 集合单元格:在内部嵌套其他单元格

前面定义了概念calculator,它的编辑器应该为:calculator name

选择Calculator概念的【Editor】页签,新增一个概念编辑器,

  1. 添加一个缩进集合类型编辑器:[-
  2. 输入常量:calculator
  3. 建立一个属性单元格编辑器
    在‘calculator’常量单元格编辑器后按Enter键,通过‘{’选择属性name:{name}

测试一下概念编辑器

  在语言项目中,点击右键选择 Generate Language 来生成语言。生成之后,我们在方案项目新建一个模型:

Model name中输入类型 jetbrains.mps.tutorial.calculator.sandbox

jetbrains.mps.tutorial.calculator语言加入到Model Properties对话框的Used Languages中,这样才能使用这个语言来编写应用:

在测试模型sandbox中新建一个Calculator概念测试应用,显示如下:

 

到现在我们已经简单建立了第一条语言,下面建立输入字段概念

给Calculator加上子概念InputField

新建一个InputField概念,设置instance can be rootfalse,实现INamedConcepts接口以便具有Name属性,然后再生成编辑器:[- input { name } -]

为了使得计算器包含输入字段,需要把InputField类型作为Calculator概念子对象:

选择在Calculator编辑器中的属性name,按Alt+Enter弹出意图列表,选择Add New Line

为了每个输入字段显示一行,我们可以对子对象InputField选择Add newline for children意图:

Ctrl+F9重新生成语言后,再次进入应用界面:

输出概念:OutputField

生成一个默认的输出概念:OutputField,编辑器如下:

把OutputField添加到calculator概念子对象中:

Calculator编辑器更改如下:

现在我们能够在outfield中输入任何表达式:

添加表达式支持

MPS中有基础语言,新的语言使用它们需要让自己的语言扩展基础语言。现在我们需要使用表达式功能,打开语言属性对话框:

Dependencies 页签中的Extended Languages 列表选择jetbrains.mps.baseLanguage:

BaseLanguage包含一个Expression概念,它表示一个表达式,格式如"2", "2+3", "abc+abc"等。这个就是我们这里示例中output需要的。

现在给OutputField添加expression子类型:

更改编辑器:

现在可以输入表达式,但是还不能通过input计算,应用界面如下:

扩展表达式概念

为了支持通过input来计算,我们需要生成自己的表达式类型。建立概念InputFieldReference,从Expression继承下来,添加InputField为引用概念field

生成InputFieldReference编辑器:%field%-> {name}表示引用概念属性

应用界面如下:MPS的智能引用会在当前上下文决定哪些引用可以使用,这里自动完成列表会列出width

定义生成器

  MPS生成器语言使用两种参数引用:property macros (marked with $) andnode macros ($$)。

  我更关注的是前面语言定义部分,这部分还没有具体看,刚兴趣的可以参考MPS1.5的示例

 

 

MPS网站

 Download MPS

 A Language Workbench in Action - MPS

 

欢迎转载,转载请注明:转载自周金根 [ http://zhoujg.cnblogs.com/ ]