创星编译器
# 创星编译器
## (一)、项目背景
- 创星编译器的最终目的,是为了实现创星集团产品前后端开发的无代码化,便于高速发展的复杂应用的敏捷开发。
- 让公司从“微服务”走向“中台化”的过程更加顺畅,真正的实现“中台之后便无代码”。
- 通过编译技术,以统一的代码规范、技术方案进行自动的代码生成,实现:
(1).全面提升开发效率,最坏情况提高2倍,最好情况提高5倍。
(2).全面改善产品质量和运行效率,最坏情况提速10%,最好情况提速1000%以上。
(3).帮助推进创新中台在公司内部的推广和使用,为中台提供核心竞争力。
## (二)、实现方向
- 低代码的实现方向由3个部分组成:
(1).前端设计;
(2).设计一门类Json的中间语言:创星通用描述性语言,Trasen-General-Descriptive-Language(TGDL) ;
(3).生成目标语言的代码。
## (三)、编译器数据流向图

## (四)、编译器的功能部件
- ### 1. 解析器(parser)
将从前端接受到的TGDL格式的JSON数据,解析为流程图的实体graph(包含结点nodes,边edges)。
在解析器中需要做流程校验,比如:
(1).程序流程的单进单出;
(2).在有返回报错时,程序立即终止;
(3).程序的边与结点不会形成闭环,以避免程序执行死循环造成的资源浪费。
在解析成功后,解析器会将得到的流程图结构实体传给遍历器。
```python
### 解析器的部分代码块示例
class Parser{
companion object{
val JSON = jacksonObjectMapper()
fun parseGraph(jsonString: String):Graph{
return tryAndCatch(::CompilerParsingGraphException){
JSON.readValue(jsonString)
}
}
fun parseExpression(jsonString: String):Expression{
return tryAndCatch(::CompilerParsingExpressionException) {
JSON.readValue(jsonString)
}
}
fun serializeExpression(expr: Expression):String{
return tryAndCatch(::CompilerRenderingExpressionException){
JSON.writeValueAsString(expr)
}
}
}
}
```
- ### 2. 遍历器(walker)
遍历器无需考虑代码如何生成,只需要考虑访问顺序,主要职责是对流程图Graph(流程图结构实体)进行遍历。
该部件属于整个数据流向中算法最多的功能部件,可理解为如何将树型的结构转换为单向可执行的序列(Series)。
其最大的难点在于:
(1).在多分支中的结点访问顺序;
(2).多分支中的程序上下文的作用域依赖的处理;
(3).如何自动将线性的代码优化为异步的代码。
遍历器部件的主要实现:
(1).保证了程序执行的顺序;
(2).多分支情况下程序的处理;
(3).无需用户在任何操作下,可以智能捕捉到异步的最优执行方案
算法复杂度的分析:
由单向程序流程的特性,且生成的是正确可执行的代码,可倒推出其时间复杂度为最优,在空间复杂度上还有优化的空间
遍历器拿到流程图的访问顺序后,会将访问顺序交给访问器,访问器会把目标生成的代码返回。
``` python
### 遍历器的部分代码块示例
fun walk(followers:MutableSet<Node>, isUsed:MutableMap<Node, Boolean>): Triple<ST, Set<Node>, Set<Node>> {
val currentIsUsed = isUsed.toMutableMap() // 本轮所使用的字典,记录所有节点的运行状态
var newlyUsed = mutableSetOf<Node>() // 新增已运行节点
currRecursion += 1 // 递归轮次
if(maxRecursion!=null){
if (currRecursion > maxRecursion) exitProcess(1)
}
val currentCodes = mutableSetOf<ST>() // 本轮代码
val nextRound = mutableSetOf<Node>() // 下一轮候选节点
```
- ### 3. 访问器(visitor)
在访问器下,不用考虑具体的访问顺序,只需要考虑具体业务的逻辑,从而在遍历器的控制下,对每个结点
(SQL,Service,Condition,Graph等)进行访问。不用考虑该如何生成代码,也不用考虑具体语法,方法定义只需要调用渲染器的接口
即可。调用完渲染器的接口后再将String格式的数据返回给遍历器,主要解决的是稳定的数据结构和易变的操作的耦合问题,
即将数据结构与数据操作分离。
```python
### 访问器的部分代码块示例
open class Visitor(open val exporter: Exporter):VisitorInterface{
open fun visitGeneralNode(
node: Node
):ST {
return when(node) {
is ServiceNode -> onServiceNode(node) // 生成本候选节点的代码
is ReturnNode -> onReturnNode(node)
is AssignNode -> onAssignNode(node)
is CallNode -> onCallNode(node)
is ErrorNode -> onErrorNode(node)
is ExitNode -> onExitNode(node)
is GraphNode -> onGraphNode(node)
else -> throw Exception("不支持的节点类型:${node.type}")
}
}
```
- ### 4. 渲染器(renderer)
将语法模板开放为一个可被调用的接口,通过配置渲染器的参数,可以调用不同的模板,从而达到实现多语言代码生成的目标。
在调用StringTemplate语法模板接口成功后,将被返回给访问器。
```python
open class Renderer(
val target: CompileTarget, // 用于定义生成目标是什么,python ? 还是 java ?
val templateRootPath: String? = null,
val enableAsyncMode:Boolean = true, // 用来控制是否生成 异步代码,要注意的是,如果业务没有大量的IO,异步的效率是不如同步快的。
val inspectMode: Boolean = false // 可以开启 Stringtemplate 自带的Debug模式,不过我们自己很少用这个
){
val globalContextName = "context" // 全局上下文的变量名,用来保存所以服务的运行结果和入参
val dependencyEngine = DependencyEngine()
val globalNameEngine = GlobalNameEngine()
open val primitiveEngine: PrimitiveEngine by lazy{ PrimitiveEngine(this) }
open val expressionEngine: ExpressionEngine by lazy{ ExpressionEngine(this) }
open val callEngine: CallEngine by lazy{ CallEngine(this) }
open val mappingEngine: MappingEngine by lazy{ MappingEngine(this) }// 这里时对所有要用到的engine类进行初始化,Renderer只是一个接口,代码生成的逻辑都定义在engine类里
private val targetPathEngine: TargetPathEngine by lazy{ TargetPathEngine(this) }
private val functionEngine: FunctionEngine by lazy{ FunctionEngine(this) }
private val statementEngine: StatementEngine by lazy{ StatementEngine(this) }
private val collectionEngine: CollectionEngine by lazy{ CollectionEngine(this) }
private val operatorEngine: OperatorEngine by lazy{ OperatorEngine(this) }
private val analysisEngine: AnalysisEngine by lazy{ AnalysisEngine(this) }
```
- ### 5. 生成器(exporter)
接受来自遍历器的代码块,生成器的职责是将这些代码块依次存储下来,再注入到基础框架的模板中,最终统一生成代码。
```kotlin
### 生成器的部分代码块示例
open class Exporter(
val target: CompileTarget, // 用于定义生成目标是什么,python ? 还是 java ?
val templateRootPath: String? = null,
var initContext:Expression = MapLiteral(hashMapOf()), // 可以给业务json里的定义和这里的定义都会被运行时的入参给覆盖。
val enableAsyncMode:Boolean = true, // 用来控制是否生成 异步代码,要注意的是,如果业务没有大量的IO,异步的效率是不如同步快的。
val runnable:Boolean = false, // 生成的代码本身是否可以直接运行? 当为false时,代码将需要被第三方引入后调用main方法运行。
val inspectMode: Boolean = false // 可以开启 Stringtemplate 自带的Debug模式,不过我们自己很少用这个)
```
- ### 6. 语法模板(Template)
基于StringTemplate的模板引擎库,可以通过形式化证明实现严格的MVC分层,用于生成模板化的代码文本。
```stg
### 语法模板的部分代码块示例
sorted(obj, lambda) ::= <%
sorted(<obj><if(lambda)>, key = <lambda><endif>)
%>
count(obj, args) ::= <%
<obj>.count(<args>)
%>
copy_imports() ::= "from copy import deepcopy"
copy(obj) ::= <%
deepcopy(<obj>)
%>
ceil_imports() ::= "from math import ceil"
ceil(obj) ::= <%
ceil(<obj>)
%>
floor_imports() ::= "from math import floor"
floor(obj) ::= <%
floor(<obj>)
%>
```
## (五)、编译器的三大保证
- 如何保证生成代码的性能
- 如何保证编译器的可扩展性
- 如何实现项目的可维护性
## (六)、未来展望
编译器作为作为无代码开发平台核心的组件,对于带动整个中台崛起扮演着领头羊的角色
- 通过异步编译来加速代码生成。
- 对算法进行优化,减少空间占用。
- 支持更多的编译目标。
- 支持更复杂的业务代码生成。