一、产品介绍
1.用户语言概述
用户语言起源于基础逻辑语句,是为了缩短开发周期,快速实现业务功能,在电力系统维护环境中应运而生。用户语言特有的接近于口语化的逻辑语句,能让不懂开发技术的用户实现自己对业务需求的配置。

用户语言3.0版本具备了云执行能力,并引入了状态机执行机制。
本版本4.0版本,产品将着重细化语言能力与边界。在语言语法方面,产品划分领域语言和标准语言2部分。在软件方面,编译器软件对应划分领域语言编译器及标准编译器2大子系统;虚拟机软件则在原有能力上新增云端执行引擎功能,并通过所需硬件能力作为边界,细分标准虚拟机及微虚拟机2大产品线。
软件的架构由领域语言编译器和语言内核2部分组成,如下图所示:

语言内核包括标准编译器、标准虚拟机、及微虚拟机。语言内核整体架构如下所示:

2.名词解释
名词 | 说明 |
标准语言 | 标准语言是用户语言标准语句的组成规则。该规则屏蔽了各行业、领域间的业务特性。详细内容见《1-用户语言4.0语言内核定义与设计_标准语言》。 |
领域语言 | 领域语言是在标准语言的基础上进行衍生扩展,形成的行业、领域里特有的方言(规则)。(如电子菜谱语言、智能照明语言等) |
标准源程序 | 标准源程序是标准编译器输入数据的数据格式。详细内容见《1-用户语言4.0语言内核定义与设计_标准语言》。 |
标准编译器 | 标准编译器负责对标准源程序进行编译,结合标准源程序中的变量表、函数表等输入数据,将标准语言语句翻译为虚拟机字节码。 |
领域编译器 | 领域编译器负责对领域语言编写的文本进行翻译,输出标准源程序。 |
ULC字节码 | ULC字节码是标准编译器输出数据的数据格式,是虚拟机字节码的一种。详细内容见《3-用户语言4.0语言内核定义与设计_ULC字节码》。 |
标准虚拟机 | 标准虚拟机是一个通过解析ULC字节码来执行逻辑指令的软件程序。详细内容见《4-用户语言4.0语言内核定义与设计_标准虚拟机》。 |
微虚拟机 | 微虚拟机是通过对标准虚拟机能力进行裁剪,得到的微型化标准虚拟机。主要提供给有硬件资源限制的设备使用。详细内容见《5-用户语言4.0语言内核定义与设计_微虚拟机》。 |
3.标准编译器

标准编译器负责对标准源程序进行编译,翻译为ULC字节码。
注:ULC字节码中只存储了标准编译器生成的,变量和函数的ID。变量和函数都需由虚拟机SDK使用者映射到真实变量及函数实现上。
标准编译器SDK包含一个命令行工具CLI及二次开发使用的标准编译库,如下所示:

3.1使用对象
主要面向领域语言编译器的开发者,和用户语言IDE(集成开发环境)工具的开发者使用。
3.2主要流程

CLI命令行工具"lc.exe"接受一个标准源程序文件input.txt。在将该文件读取到字符串inputTxt后,CLI将其作为输入参数,调用编译库提供的标准编译接口,得到输出数据ULC字节码。CLI再将ULC字节码保存到文件中,即获得down.ulc文件。
另外,CLI命令行工具实现的日志回调实现会将编译过程中产生的编译日志写入down.log文件中。
二、标准编译库
标准编译器SDK是标准编译器的一个软件开发套件(Software Development Kit),使用者可基于SDK提供的标准编译库进行软件二次开发。目前只提供了C++SDK,需使用C++作为开发语言。
1.Hello world!
以下我们通过1个简单的实例来展示SDK标准编译库的使用。
首先保存下述标准源程序文本(标准源程序的编写见《1-用户语言4.0语言内核定义与设计_标准语言》):
<<变量表>>
变量名称 | 数据类型 | 唯一标识
var1 | int32 | key1
var2 | int32 | key2
<<常量表>>
常量名称 | 数据类型 | 常量值
CONST1 | int32 | 1
<<函数表>>
函数名称 | 参数列表 | 返回值类型 | 唯一标识
FUNC1 | int8,int16 | void | key3
FUNC2 | | void | key4
<<语句表>>
语句内容
var1==1 ? var2==CONST1 @ FUNC1(1,1) : FUNC2() !
将文本存储到input中,使用SDK编写以下程序:
#include "./include/Compiler.h"
int main()
{
LanugageV4::UFile input = …
std::map<LanugageV4::U_FILE_TYPE, LanugageV4::UFile> output;
char ok = 0;
// 创建编译器对象
LanugageV4::CCompiler compiler;
// 调用标准编译接口
ok = compiler.Compile(input, output);
// 编译成功,将output中的ULC字节码取出保存
if (ok)
{
LanugageV4::UFile ulc = output[LanugageV4::U_FILE_TYPE_ULC];
}
}
运行上述程序,将获得1个包含ULC字节码的输出结果,详细见章节2.3.1.1“Compile”。
2.头文件说明
头文件名称 | 说明 |
./include/Compiler.h | 编译器类的头文件,对外提供标准编译相关接口。 |
./include/Config.h | 保存平台宏配置,及编译配置枚举类的头文件。 |
./include/LogInfo.h | 提供日志相关接口的头文件。 |
3.函数定义
3.1.标准编译器提供的接口
3.1.1.编译器类CCompiler
所属头文件
Compiler.h
函数
名称 | 描述 |
CCompiler | 编译器类的构造函数。 |
Compile | 标准编译接口。 |
SetConfig | 编译配置的设置接口。 |
SetLogout | 日志回调的设置接口。 |
如需修改编译配置及日志回调,在调用Compile前应先调用SetConfig及SetLogout接口,使之生效。
3.1.1.1. CCompile
编译器类的构造函数,对外提供为LanguageV4::CCompiler::CCompiler。
接口定义
class CCompiler
{
public:
explicit CCompiler();
};
示例
LanugageV4::CCompiler compiler;
// 或
LanugageV4::CCompiler* pCompiler = new LanguageV4::CCompiler();
…
delete pCompiler;
3.1.1.2. Compile
标准编译器SDK基于标准编译流程封装了各个核心模块的使用过程,对外提供为标准编译接口LanguageV4::CCompiler::Compile。由于编译核心模块暂不对外使用,使用者必须使用标准编译接口对源程序进行编译:
接口定义
bool Compile(const UFile& input, std::map<UFileType, UFile>& output);
输入参数
参数名称 | 参数说明 | 备注 |
input | input是使用UFile数据结构的输入参数,它保存了标准源程序文本基于UTF-8编码的字节流。 |
UFile是SDK提供的一个字节流存储专用数据结构:
typedef struct _UFile
{
char* data; // 字节流内容
size_t size; // 字节流长度
} UFile;
输出参数
参数名称 | 参数说明 | 备注 |
output | 一个<key,value>结构。其中key=U_FILE_TYPE_ULC的字段上保存基于UFile存储的ULC字节码。ULC字节码详细内容见《3-用户语言4.0语言内核定义与设计_ULC字节码》。 |
UFileType是SDK提供的上述输出参数的key的枚举值:
enum UFileType
{
U_FILE_TYPE_ULC = 0, // ULC字节码
U_FILE_TYPE_CFG = 1 // CLI专用运行配置
};
返回值
成功返回true,否则返回false。
示例
…
LanugageV4::CCompiler* compiler = new LanugageV4::CCompiler();
ok = compiler->Compile(input, output);
if (ok)
{
LanugageV4::UFile ulc = output[LanugageV4::U_FILE_TYPE_ULC];
…
}
…
3.1.1.3. SetConfig(可选)
SDK的提供了编译配置的设置接口LanguageV4::CCompiler::SetConfig,来对标准编译过程进行配置。使用者需在调用Compile接口前先调用SetConfig接口修改编译配置,使之起效。
接口定义
bool SetConfig(std::map<UConfigType, std::string>& properties);
输入参数
参数名称 | 参数说明 | 备注 |
properties | properties是一个<key,value>数据结构。其中key是SDK约定的编译配置项枚举值;value则是具体配置值。 |
UConfigType是SDK提供的上述输入参数的key的枚举值:
enum UConfigType
{
U_CONFIG_VM_FILE_TYPE = 0, // 虚拟机文件类型
U_CONFIG_VM_FILE_VERSION = 1, // 虚拟机文件格式版本号
U_CONFIG_LOGOUT_LEVEL = 2, // 日志输出等级
U_CONFIG_VM_FILE_VAR = 3, // 变量打包微型化开关
U_CONFIG_VM_FILE_FUN = 4, // 函数打包微型化开关
U_CONFIG_MAX_LOGIC_SEG_BYTES = 5, // 语句长度限制
U_CONFIG_MAX_TRIGGERED = 6 // 同时触发语句总数限制
};
如上所述,SDK目前提供了7个可配置项。编译配置是非必需的,SDK会对未进行配置的配置项使用默认配置值:

返回值
生效返回true,否则返回false。配置项超出枚举范围,或配置值超出取值范围都将导致当次配置不生效,即返回false时,当次所设置的所有编译配置内容将不起效。
示例
…
std::map<UConfigType, std::string> properties;
properties[UConfigType::U_CONFIG_LOGOUT_LEVEL] = "2";
ok = compiler.SetConfig(properties);
…
if (ok)
{
compiler.Compile(input, output);
}
…
文件微型化说明
文件微型化是指对ULC字节码中事件、逻辑语句外的数据进行裁剪,达到微型化的效果:

ULC字节码详细内容见《3-用户语言4.0语言内核定义与设计_ULC字节码》。
3.1.1.4.SetLogout(可选)
SDK默认只会产生但不会输出/显示日志文本。若需要获取编译日志内容,需为SDK实现并通过LanguageV4::CCompiler::SetLogout设置日志回调。使用者需在调用Compile接口前先调用SetLogout接口设置日志回调,使之起效。
接口定义
void SetLogout(const std::string& logctx, const ULogCallback logCB);
输入参数
参数名称 | 参数说明 | 备注 |
logctx | 日志上下文。标准编译器调用日志回调时会传出logctx,日志回调实现逻辑中可配合logctx决定输出行为。 | |
logCB | 日志回调实现ULogCallback 的函数句柄。详细内容见2.3.1.1“ULogCallback”日志回调接口。 |
示例
#include "./include/LogInfo.h"
void MyLogout(const std::string& logctx, const LanugageV4::CCompileLogInfo* message){…}
…
compiler.SetLogout("./project/my.log", MyLogout);
…
compiler.Compile(input, output);
…
注:
1、 需要使用logctx作为input的唯一标识时,需在每次调Compile前都调用一次SetLogout进行logctx的更新;
2、logctx由使用者自行生成并决定其用途,SDK仅负责将其传送到使用者实现的日志回调接口上。
3.1.2.编译日志容器类 CCompileLogInfo
所属头文件
LogInfo.h
函数
名称 | 描述 |
ToString | 格式化输出日志信息。 |
GetLevel | 获取日志等级。 |
GetCode | 获取错误码。 |
GetRowNo | 获取语句编号。 |
GetPos | 获取字符位置。 |
GetModule | 获取模块类型。 |
GetMessageInfo | 获取日志概要。 |
GetKeywords | 关键字。 |
编译日志对外以LanugageV4::CCompileLogInfo对象的形式逐条输出。
3.1.2.1.ToString
日志容器类的LanugageV4::CCompileLogInfo::ToString函数可按默认日志格式对当前日志进行文本化输出:
接口定义
std::string ToString() const;
返回值
按下述格式返回文本化的编译日志:
${module.name}:${row}:${pos}: ${level}(${code}): $detail
数据层加载:2:20: error(3001): 变量名:var1; 变量名不符合命名规则
其中module、row、pos等字段为成员变量,不对外公开,需使用相应Getter进行取值。Getter定义见后续章节。
3.1.2.2.GetLevel
日志容器类的LanugageV4::CCompileLogInfo::GetLevel日志等级获取函数。从编译日志容器获取当前日志等级的枚举值。
接口定义
LanugageV4::CompileLogLevel GetLevel() const;
返回值
日志等级的枚举值。CompileLogLevel是SDK提供的日志等级枚举值:
enum CompileLogLevel
{
Debug = 0,//无关紧要的信息,例如一些调试信息,请使用此值。
Info, //比较重要的信息,例如“编译结束!”等,请使用此值。请不要滥用此值。
Warning, //警告信息。
Error, //错误信息。
};
3.1.2.3.GetCode
日志容器类的LanugageV4::CCompileLogInfo::GetCode错误码获取函数。从编译日志容器获取当前日志的错误码。
接口定义
int GetCode() const;
返回值
错误码详细内容见附录A“标准编译库错误码”。
3.1.2.4. GetRowNo
日志容器类的LanugageV4::CCompileLogInfo::GetRowNo所属行号获取函数。从编译日志容器获取当前日志所属的行号。
接口定义
int GetRowNo() const;
返回值
日志所属行号。其中,获得的错误码范围(见2.3.1.2.3“GetCode”)在3000~3599时,所属行号内容为标准源程序对应行号;获得的错误码范围在3600~4999时,所属行号内容为语句表语句序号(从1开始)。行号显示为0时,表示无错误或日志内容与标准源程序无关。
3.1.2.5.GetPos
日志容器类的LanugageV4::CCompileLogInfo::GetPos日志所属语句字符位置获取函数。从编译日志容器获取当前日志所属语句的字符位置。
接口定义
int GetPos() const;
返回值
日志所属语句字符位置。其中,获得的错误码范围(见2.3.1.2.3“GetCode”)在3600~4999时,所属语句字符位置才有非0值。字符位置显示为0时,表示无错误或日志内容与语句无关。
3.1.2.6.GetModule
日志容器类的LanugageV4::CCompileLogInfo::GetModule日志模块获取函数。从编译日志容器获取当前日志所属编译模块的枚举值。
接口定义
LanugageV4::CompileModule GetModule() const;
返回值
日志所属编译模块的枚举值。CompileModule
是SDK提供的编译模块的枚举值:
enum CompileLogLevel
{
EGlobal = 0, //全局
ELanguageData, //数据层加载
EPreCompile, //预编译
ELexer, //词法分析
EParser, //语法分析
ESemanticAnalyzer, //语义分析
EIntermediateCode, //中间代码
EOptimizer, //代码优化器
ETargetCodeGenerator, //目标代码生成器
EMakefile //文件打包器
};
3.1.2.7. GetMessageInfo
日志容器类的LanugageV4::CCompileLogInfo::GetMessageInfo日志概要获取函数。从编译日志容器获取当前日志的概要信息。
接口定义
const std::string& GetMessageInfo() const;
返回值
日志概要。详细内容见附录A“标准编译库错误码”表格中的错误说明相关内容。
3.1.2.8.GetKeywords
日志容器类的LanugageV4::CCompileLogInfo::GetKeywords日志关联关键字获取函数。从编译日志容器获取当前日志关联的关键字及内容。
日志关联关键字的作用于日志概要类似:日志概要用于存储当前错误码下通用的错误描述,而日志关联关键字则定义了当前错误码关联的具体错误原因,并使用K-V结构记录每个错误原因在标准源程序中的对应内容。
接口定义
std::map<std::string, std::string>& GetKeywords() const;
返回值
当前日志关联的关键字及内容。
示例
使用下述标准源程序:
<<变量表>>
变量名称 | 数据类型 | 唯一标识
var1 | int32 | key1
| int32 | key2
…
该源程序存在变量名称为空的变量,使用此源程序进行编译,应产生下述错误:
日志等级 | 错误码 | 关键字 | 错误说明 |
error | 3000 | 变量id | 变量名为空 |
调用GetKeywords()将获得K-V对:
{
变量id: 0
}
调用ToString()将获得下述格式化日志内容:
数据层加载:4:-1: error(3000): 变量名id:0; 变量名为空
3.1.3.全局函数
函数
名称 | 头文件 | 描述 |
GetSdkVersion | Compiler.h | 获取SDK版本号 |
3.1.3.1.GetSdkVersion
获取SDK版本号的函数。
接口定义
std::string GetSdkVersion();
返回值
返回标准编译器SDK中,标准编译库的版本号。
3.2.接入方需要实现的接口
所属头文件
Compiler.h
函数(签名)
名称 | 描述 |
ULogCallback | 日志回调函数(签名)。 |
3.2.1.ULogCallback
ULogCallback 是SDK约定的日志回调模块的函数签名,使用者需基于该签名实现日志回调模块:
// 日志回调函数(函数签名)
typedef void(*ULogCallback)(const std::string& logctx, const CCompileLogInfo* message);
输入参数
参数名称 | 参数说明 | 备注 |
logctx | 标准编译器调用日志回调时传出的logctx,与设置日志回调的logctx一致。主要用于识别当前编译内容。 | |
message | 保存了单条编译日志的容器,具体日志容器类说明。SDK的每条编译日志都单独调用日志回调对外输出。 |
示例
void MyLogout(const std::string& logctx, const CCompileLogInfo* message)
{
// 使用“日志默认格式文本化函数”打印日志
… std::cout << message->ToString() << std::endl;
// 该次标准编译过程中,logctx就是下面设置日志回调时使用的"./project/my.log"
… fwrite(logctx, message->ToString());
}
…
Compiler* compiler = new LanguageV4::Compiler();
compiler->SetLogout("./project/my.log", MyLogout);
…
compiler->Compile(input, output);
…
三、命令行工具CLI
CLI是标准编译器提供的一个命令行工具(Command Line Interface),是一个基于标准编译库开发的可执行程序。使用者可通过CLI进行标准源程序的批量编译,或是利用CLI生成的编译日志进行回归测试。
1.Hello world!
以下我们通过1个简单的实例在linux系统展示CLI的使用。
将输入数据(见2.1“Hello
world!”相关内容)保存到文件input.txt中,输入数据的编写见《1-用户语言4.0语言内核定义与设计_标准语言》。使用CLI编译上述文件,输出结果如下:
$ lc -i input.txt
总用时:394毫秒
编译成功!
使用linux的ls指令可看到当前文件夹下新增的2个文件:down.ulc,log.txt:
$ ls
down.ulc input.txt lc lc.properties log.txt …
使用linux的cat指令可查看down.log的日志内容:
$ cat log.txt
全局:-1:-1: Info(0) compiler core version: 4.00.00.04
全局:-1:-1: Info(0) 开始预编译!
预编译:-1:-1: Debug(0) var1==1 ? var2==CONST1 @ FUNC1(1,1) : FUNC2() !
…
2.使用方式
标准编译器CLI是一个命令行工具,可将软件位置注册到环境变量中,即可在任意路径下使用。命令格式如下:
$ lc [options] [file]
其中options是CLI支持的运行选项,file需填入标准源程序的文件名(路径)。
另外地,在与lc相同路径下还有一个编译配置文件lc.properties。使用者可通过修改配置值调整编译配置。编译配置详细内容见章节2.2.1.2“SetConfig”。
2.1.运行选项

2.2.使用示例
$ lc -i input.txt
$ lc --input input.txt
$ lc -i input.txt -l ./project/log.txt
$ lc -v
CLI version:1.0.0.4(SDK version:4.00.00.04)
Copyright @ 2021 All Rights Reserved 广东优特云科技有限公司版权所有
四、运行环境
标准编译器SDK提供了linux、windows、android、ios多个平台的C++SDK。
附录A、标准编译库错误码
