Skip to content

Commit 700e569

Browse files
committed
docs: synced via GitHub Actions
1 parent 5d60d44 commit 700e569

File tree

1 file changed

+318
-0
lines changed

1 file changed

+318
-0
lines changed
Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
# Nop入门:增加DSL模型解析器
2+
3+
Nop平台为开发自定义DSL(Domain Specific Language,领域特定语言)提供了一系列基础设施,核心机制是通过XDef元模型(Nop平台的元模型语言)定义XML格式的语法结构,然后自动根据XDef生成AST(Abstract Syntax Tree,抽象语法树)节点类,并实现解析器、
4+
生成器、验证器等工具。具体介绍参见[XDef:一种面向演化的元模型及其构造哲学](https://mp.weixin.qq.com/s/gEvFblzpQghOfr9qzVRydA)
5+
6+
虽然XDef元模型语言采用XML语法格式,但这并不意味着Nop平台中的DSL只能使用XML格式表达。实际上,根据**可逆计算理论**, 同一信息结构具有多种表达形式,这些表达形式之间可以自动进行可逆转换。
7+
8+
本文将介绍如何为DSL引入Markdown语法格式,并演示如何实现Markdown与XML/JSON之间的双向转换。
9+
10+
为了更好地理解如何扩展DSL加载器,我们首先需要了解Nop平台的核心模块结构:
11+
12+
## 1. nop-kernel核心模块
13+
14+
Nop平台内置了丰富的模块,但用于支持DSL定义和实现的核心部分(也是可逆计算理论的实现核心)完全集中在`nop-kernel`目录下。该目录包含以下关键子模块:
15+
16+
1. **nop-dependencies**: 统一管理Nop平台使用的外部第三方依赖库版本
17+
2. **nop-api-core**: 提供各类注解以及`ApiRequest`/`ApiResponse`等框架级别的DTO定义
18+
3. **nop-commons**: 包含各种工具类(Utility Helpers)
19+
4. **nop-core**: 实现虚拟文件系统、XML/JSON等基础格式解析器和反射机制
20+
5. **nop-javac**: 对Java内置JavaC编译器的封装
21+
6. **nop-dataset**: 封装`IRecordInput`/`IDataSet`接口,统一处理各类列表数据和表格数据
22+
7. **nop-xdefs**: 集中存放Nop平台内置的所有XDef元模型
23+
8. **nop-xlang**: 实现Xpl模板语言、XScript脚本语言以及Delta合并算法
24+
9. **nop-antlr4**: 为XScript脚本语言提供antlr4扩展和封装支持
25+
10. **nop-codegen**: 提供`XCodeGenerator`代码生成器
26+
11. **nop-record-mapping**: 实现异构对象映射模型,用于辅助实现Markdown结构与一般模型对象之间的双向映射
27+
12. **nop-markdown**: 为Markdown格式的DSL解析提供额外封装,包含一个简易的Markdown解析器
28+
13. **nop-kernel-cli**: 演示用的命令行工具,提供`convert`(模型格式转换)和`gen`(模板驱动代码生成)两个指令
29+
30+
* 其中,`nop-kernel-cli`模块下的`demo`目录包含了DSL自定义解析器和自定义代码生成模板的示例
31+
32+
了解了核心模块结构后,接下来我们将学习如何为DSL注册自定义加载器:
33+
34+
## 2. 注册加载器
35+
36+
在Nop平台中,所有DSL文件格式都对应一个唯一的文件类型(fileType)。
37+
全局模型加载器`ResourceComponentManager`在加载文件时会根据文件类型动态确定使用的加载器。
38+
39+
因此,为了给`orm.xml`这种文件定义DSL加载器,需要编写`/nop/core/registry/orm.register-model.xml`
40+
41+
```xml
42+
<model x:schema="/nop/schema/register-model.xdef" xmlns:x="/nop/schema/xdsl.xdef"
43+
name="orm">
44+
<loaders>
45+
<!-- 为XML/JSON/YAML格式注册内置加载器 -->
46+
<xdsl-loader fileType="orm.xml" schemaPath="/nop/schema/orm/orm.xdef"/>
47+
<xdsl-loader fileType="orm.json" schemaPath="/nop/schema/orm/orm.xdef"/>
48+
<xdsl-loader fileType="orm.yaml" schemaPath="/nop/schema/orm/orm.xdef"/>
49+
50+
<!-- 为Markdown格式注册自定义加载器 -->
51+
<loader fileType="orm.md" mappingName="orm.Md_to_OrmModel"
52+
class="io.nop.record_mapping.md.MarkdownDslResourceLoaderFactory"/>
53+
</loaders>
54+
</model>
55+
```
56+
57+
上述配置文件定义了ORM模型的加载器注册信息:
58+
59+
- `xdsl-loader`:用于配置XML/JSON/YAML格式的模型加载器。Nop平台内置了`DslJsonResourceLoader``DslXmlResourceLoader`,它们会根据文件类型自动选择合适的解析器。
60+
- `loader`:用于定义扩展加载器,可支持自定义文件格式。
61+
- `fileType`:指定文件类型标识符
62+
- `mappingName`:指定用于解析的映射规则名称
63+
- `class`:指定加载器工厂类的实现路径,需实现`IResourceObjectLoaderFactory``IResourceObjectLoader`接口
64+
65+
以下是相关接口的定义:
66+
67+
```java
68+
// 资源对象加载器工厂接口
69+
interface IResourceObjectLoaderFactory<T> {
70+
IResourceObjectLoader<T> newResourceObjectLoader(ComponentModelConfig config, Map<String, Object> attributes);
71+
}
72+
73+
// 资源对象加载器接口
74+
interface IResourceObjectLoader<T> {
75+
T loadObjectFromResource(IResource resource);
76+
}
77+
78+
// 资源对象保存器接口
79+
interface IResourceObjectSaver<T> {
80+
void saveObjectToResource(IResource resource, T obj);
81+
}
82+
```
83+
84+
MarkdownDslResourceLoaderFactory返回的MarkdownDslResourceLoader实现了如下四个接口
85+
86+
- `IResourceObjectLoader`: 解析资源文件得到模型对象
87+
- `IResourceObjectSaver`: 将模型对象保存到资源文件中
88+
- `IResourceDslNodeLoader`: 解析资源文件得到XNode(Nop平台中通用的DSL抽象语法树节点,用于统一表示不同格式的DSL结构)
89+
- `IResourceDslNodeSaver`: 将XNode保存到资源文件中
90+
91+
除了`IResourceObjectLoader`接口之外,其他三个接口都是可选的。实现这些接口后,就可以支持双向转换功能。
92+
93+
注册完加载器后,我们需要定义Markdown与模型对象之间的映射规则。这正是`RecordMapping`机制的用武之地:
94+
95+
## 3. 基于Mapping模型的Markdown解析和生成
96+
97+
`RecordMapping`是Nop平台内置的对象映射机制,用于定义两个异构Java对象之间的相互映射规则,支持字段映射、类型转换、表达式计算等功能。
98+
在RecordMapping格式的基础上补充`md:format`等扩展字段信息,就可以实现MarkdownSection对象与DSL模型对象之间的映射。
99+
100+
```xml
101+
<definitions xmlns:x="/nop/schema/xdsl.xdef" x:schema="/nop/schema/record/record-mappings.xdef"
102+
xmlns:md="md" x:dump="true">
103+
104+
105+
<!-- 定义从Markdown到ORM模型的映射规则 -->
106+
<mapping name="Md_to_OrmModel" md:titleField="displayName">
107+
<fields>
108+
<!-- 直接映射displayName字段 -->
109+
<field name="displayName" from="displayName"/>
110+
111+
<!-- 映射extends字段,并指定别名 -->
112+
<field name="x:extends" from="extends" alias="Extends">
113+
<schema stdDomain="string"/>
114+
</field>
115+
116+
<!-- 映射实体定义,使用Md_to_EntityModel作为子项映射规则 -->
117+
<field name="entities" from="实体定义" alias="Entities" keyProp="name"
118+
itemMapping="Md_to_EntityModel">
119+
</field>
120+
</fields>
121+
</mapping>
122+
123+
<!-- 定义从Markdown到实体模型的映射规则 -->
124+
<mapping name="Md_to_EntityModel" md:titleField="name">
125+
<fields>
126+
<!-- 映射对象名,使用表达式生成完整类名 -->
127+
<field name="name" from="对象名" alias="Object Name">
128+
<schema stdDomain="class-name"/>
129+
<valueExpr>
130+
value?.$fullClassName(rootRecord['ext:entityPackageName'])
131+
</valueExpr>
132+
</field>
133+
134+
<!-- 映射表名,设置为必填字段 -->
135+
<field name="tableName" from="表名" mandatory="true" alias="Table Name">
136+
<schema stdDomain="prop-name"/>
137+
</field>
138+
139+
<!-- 映射中文名,设置为必填字段 -->
140+
<field name="displayName" from="中文名" mandatory="true" alias="Chinese Name">
141+
<schema stdDomain="string"/>
142+
</field>
143+
144+
<!-- 映射字段列表,指定为表格格式 -->
145+
<field name="columns" from="字段列表" keyProp="name"
146+
alias="Column List" itemMapping="Md_to_ColumnModel" md:format="table">
147+
</field>
148+
149+
<!-- 映射关联列表,指定为表格格式 -->
150+
<field name="relations" from="关联列表" alias="Relation List"
151+
itemMapping="Md_to_RelationModel" md:format="table">
152+
</field>
153+
154+
</fields>
155+
</mapping>
156+
157+
</definitions>
158+
```
159+
160+
Nop平台通过`MappingBasedMarkdownParser``MappingBasedMarkdownGenerator`根据Mapping模型配置实现解析与生成功能:
161+
162+
- `md:titleField`: 用于指定标题对应的解析字段
163+
- `md:format`: 指定字段对应的Markdown格式,目前支持`table|code`,分别对应表格格式和代码块格式
164+
165+
我们约定了简单的Markdown编码规则:
166+
1. 每个section只能是List/Table/CodeBlock等几种格式
167+
2. 通过子section实现复杂对象,标题对应属性名
168+
3. 若section对应于List,则每个子section对应一个对象,此时section的标题对应对象的titleField
169+
170+
以下是一个完整的Markdown格式ORM模型示例:
171+
172+
````markdown
173+
# AI模型管理
174+
175+
- extends: demo.orm.md
176+
177+
## gen-extends
178+
```xml
179+
<orm-gen:DefaultPostExtends xpl:lib="/xlib/orm-gen.xlib"/>
180+
```
181+
182+
## 5 实体定义
183+
184+
### 5.1 NopAiProject
185+
186+
- 表名: nop_ai_project
187+
- 类名: NopAiProject
188+
- 中文名: AI项目
189+
- 备注: 存储AI项目基本信息
190+
191+
#### 5.1.1 字段列表
192+
193+
|编号|标签|主键|非空|字段名|属性名|显示|中文名|英文名|数据域|标准域|类型|长度|小数位数|字典|备注|缺省值|控件|根节点级别|
194+
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
195+
| 1 | seq | true | true | id | id | X | 主键 | | | | VARCHAR | 36 | | | | | | |
196+
| 2 | | false | true | language | language | | 项目语言 | | | | VARCHAR | 4 | | ai/project_language | 项目使用的编程语言类型:JAVA, PYTHON等 | | | |
197+
| 3 | | false | true | name | name | | 项目名称 | | | | VARCHAR | 100 | | | | | | |
198+
| 4 | | false | false | prototype_id | prototypeId | | 模板项目ID | | | | VARCHAR | 36 | | | | | | |
199+
| 5 | | false | false | project_dir | projectDir | | 项目目录 | | | | VARCHAR | 400 | | | 项目在文件系统中的存储路径,例如:/data/projects/order-system | | textarea | |
200+
201+
````
202+
203+
上述Markdown文件解析后得到的XML格式如下:
204+
205+
```xml
206+
<orm ext:mavenArtifactId="nop-ai" ext:entityPackageName="io.nop.ai.dao.entity" ext:allowIdAsColName="true"
207+
ext:basePackageName="io.nop.ai" ext:appName="nop-ai" ext:registerShortName="true"
208+
ext:mavenGroupId="io.github.entropy-cloud" x:schema="/nop/schema/orm/orm.xdef" xmlns:x="/nop/schema/xdsl.xdef"
209+
xmlns:ext="ext" displayName="AI模型管理"
210+
xmlns:ui="ui">
211+
<entities>
212+
<entity className="io.nop.ai.dao.entity.NopAiProject" displayName="AI项目" name="io.nop.ai.dao.entity.NopAiProject"
213+
registerShortName="true" tableName="nop_ai_project">
214+
<columns>
215+
<column code="id" displayName="主键" mandatory="true" name="id" precision="36" primary="true" propId="1"
216+
stdDataType="string" stdSqlType="VARCHAR" tagSet="seq" ui:show="X"/>
217+
<column code="language" comment="项目使用的编程语言类型:JAVA, PYTHON等" displayName="项目语言"
218+
mandatory="true"
219+
name="language" precision="4" propId="2" stdDataType="string" stdSqlType="VARCHAR"
220+
ext:dict="ai/project_language"/>
221+
<column code="name" displayName="项目名称" mandatory="true" name="name" precision="100" propId="3"
222+
stdDataType="string" stdSqlType="VARCHAR"/>
223+
<column code="prototype_id" displayName="模板项目ID" name="prototypeId" precision="36" propId="4"
224+
stdDataType="string" stdSqlType="VARCHAR"/>
225+
<column code="project_dir" comment="项目在文件系统中的存储路径,例如:/data/projects/order-system"
226+
displayName="项目目录"
227+
name="projectDir" precision="400" propId="5" stdDataType="string" stdSqlType="VARCHAR"
228+
ui:control="textarea"/>
229+
</columns>
230+
<comment>存储AI项目基本信息</comment>
231+
</entity>
232+
</entities>
233+
</orm>
234+
```
235+
236+
可以看到,Markdown中定义的字段列表(表格格式)被正确转换为XML中的`<columns>`节点,包括字段名称、数据类型、约束条件等信息都被准确映射。
237+
238+
完成了加载器注册和映射规则定义后,我们就可以使用`nop-kernel-cli`工具进行实际的模型转换和代码生成操作了:
239+
240+
## 4. 通过元编程自动生成反向映射
241+
242+
Nop平台提供了强大的元编程能力,可以根据已定义的正向映射自动生成反向映射配置。例如,当我们定义了从Markdown到ORM模型的映射`Md_to_OrmModel`后,系统可以自动生成从ORM模型到Markdown的反向映射`OrmModel_to_Md`
243+
244+
### 4.1 自动生成反向映射的配置
245+
246+
通过在RecordMapping配置中添加`record-mapping-gen:GenReverseMappings`扩展,系统会自动为所有正向映射生成对应的反向映射:
247+
248+
```xml
249+
<definitions xmlns:x="/nop/schema/xdsl.xdef" x:schema="/nop/schema/record/record-mappings.xdef"
250+
xmlns:md="md" x:dump="true">
251+
<x:post-extends>
252+
<c:import from="/nop/record/xlib/record-mapping-gen.xlib"/>
253+
<record-mapping-gen:GenReverseMappings/>
254+
</x:post-extends>
255+
256+
<!-- 正向映射配置 -->
257+
<mapping name="Md_to_OrmModel" ...>
258+
<!-- 正向映射规则 -->
259+
</mapping>
260+
261+
<!-- 系统会自动生成OrmModel_to_Md反向映射 -->
262+
</definitions>
263+
```
264+
265+
执行后,系统会自动生成名为`OrmModel_to_Md`的反向映射,其字段映射关系与`Md_to_OrmModel`相反,确保数据可以在两种格式之间无损转换。
266+
267+
### 4.2 差量修正反向映射配置
268+
269+
自动生成的反向映射可能无法完全满足所有需求,这时可以在当前文件中添加差量修正部分来微调配置。根据可逆计算理论,差量定义与全量定义格式完全一致。
270+
271+
```xml
272+
<definitions xmlns:x="/nop/schema/xdsl.xdef" x:schema="/nop/schema/record/record-mappings.xdef"
273+
xmlns:md="md" x:dump="true">
274+
<x:post-extends>
275+
<c:import from="/nop/record/xlib/record-mapping-gen.xlib"/>
276+
<record-mapping-gen:GenReverseMappings/>
277+
</x:post-extends>
278+
279+
<!-- 正向映射配置 -->
280+
<mapping name="Md_to_OrmModel" ...>
281+
<!-- 正向映射规则 -->
282+
</mapping>
283+
284+
<mapping name="EntityModel_to_Md">
285+
<fields>
286+
<field name="对象名">
287+
<valueExpr>
288+
value?.$removePackageName(sourceRoot['ext:entityPackageName']);
289+
</valueExpr>
290+
</field>
291+
292+
<field name="类名">
293+
<valueExpr>
294+
value?.$removePackageName(sourceRoot['ext:entityPackageName']);
295+
</valueExpr>
296+
</field>
297+
</fields>
298+
</mapping>
299+
</definitions>
300+
```
301+
302+
303+
## 5. 使用nop-kernel-cli执行模型转换和代码生成
304+
305+
完成DSL加载器配置后,可以使用`nop-kernel-cli`工具执行模型格式转换和代码生成操作。以下是常用命令示例:
306+
307+
```shell
308+
# 将XML格式的ORM模型转换为Markdown格式
309+
java -jar nop-kernel-cli.jar convert demo.orm.xml -o=demo.orm.md
310+
311+
# 将Markdown格式的ORM模型转换回XML格式
312+
java -jar nop-kernel-cli.jar convert demo.orm.md -o=demo.orm.xml
313+
314+
# 基于Markdown格式的ORM模型生成代码
315+
java -jar nop-kernel-cli.jar gen demo.orm.md -t=/nop/templates/orm -o=target
316+
```
317+
318+
通过上述命令,我们可以实现不同格式DSL模型之间的双向转换,以及基于这些模型的代码生成,充分体现了Nop平台的灵活性和可逆计算理论的实践价值。

0 commit comments

Comments
 (0)