创星编译器

# 创星编译器 ## (一)、项目背景 - 创星编译器的最终目的,是为了实现创星集团产品前后端开发的无代码化,便于高速发展的复杂应用的敏捷开发。 - 让公司从“微服务”走向“中台化”的过程更加顺畅,真正的实现“中台之后便无代码”。 - 通过编译技术,以统一的代码规范、技术方案进行自动的代码生成,实现: (1).全面提升开发效率,最坏情况提高2倍,最好情况提高5倍。 (2).全面改善产品质量和运行效率,最坏情况提速10%,最好情况提速1000%以上。 (3).帮助推进创新中台在公司内部的推广和使用,为中台提供核心竞争力。 ## (二)、实现方向 - 低代码的实现方向由3个部分组成: (1).前端设计; (2).设计一门类Json的中间语言:创星通用描述性语言,Trasen-General-Descriptive-Language(TGDL) ; (3).生成目标语言的代码。 ## (三)、编译器数据流向图 ![image.png](https://cos.easydoc.net/92634618/files/knxz3brc.png) ## (四)、编译器的功能部件 - ### 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>) %> ``` ## (五)、编译器的三大保证 - 如何保证生成代码的性能 - 如何保证编译器的可扩展性 - 如何实现项目的可维护性 ## (六)、未来展望 编译器作为作为无代码开发平台核心的组件,对于带动整个中台崛起扮演着领头羊的角色 - 通过异步编译来加速代码生成。 - 对算法进行优化,减少空间占用。 - 支持更多的编译目标。 - 支持更复杂的业务代码生成。