主要内容
- 本篇博客主要介绍的是通过将
antlr4
集成到react-diagram(typescript)
框架中。
- 这个集成过程是web应用可视化编程(类似于unreal blueprint)的一个子任务。
背景
antlr4
是一个将代码转换为语法树结构的应用,用户能通过内置的API对语法树进行遍历,从而获取其内部的变量,逻辑信息。
- 我们目前这个迭代的主要目标是:将用户的代码转换为一个序列化的JSON,这个序列化的JSON通过
react-diagram
的反序列化引擎,可以直接显示成易于理解的可视化图像。
示例
a. 用户代码
Rule3=[Transfer.to NOT IN BlackList]
b. 序列化JSON
- 将用户代码转换为下列JSON。
- 下面的序列化JSON为手动生成,目前的目标就是自动生成这个序列化的JSON。
{
"id": "27",
"offsetX": 0,
"offsetY": 0,
"zoom": 100,
"gridSize": 0,
"layers": [{
"id": "28",
"type": "diagram-links",
"isSvg": true,
"transformed": true,
"models": {
"36": {
"id": "36",
"type": "default",
"source": "32",
"sourcePort": "33",
"target": "34",
"targetPort": "35",
"points": [{
"id": "37",
"type": "point",
"x": 0,
"y": 0
}, {
"id": "38",
"type": "point",
"x": 0,
"y": 0
}],
"labels": [],
"width": 2,
"color": "grey",
"curvyness": 50,
"selectedColor": "rgb(0,192,255)"
}
}
}, {
"id": "30",
"type": "diagram-nodes",
"isSvg": false,
"transformed": true,
"models": {
"32": {
"id": "32",
"type": "default",
"x": 100,
"y": 100,
"ports": [{
"id": "33",
"type": "default",
"x": 100,
"y": 100,
"name": "to",
"alignment": "right",
"parentNode": "32",
"links": ["36"],
"in": false,
"label": "To"
}],
"name": "Transfer",
"color": "rgb(0,192,255)",
"portsInOrder": [],
"portsOutOrder": ["33"]
c. react-diagram引擎渲染
- 为了达成从用户代码到react-diagram引擎的过程,我们需要通过antlr4引擎对代码进行语法树的处理。
语法树
实现
1. 搭建react-diagram
- 不要下载最新版本的
react-diagram
,到这个链接下载v6.2.0的版本。
- 到本地解压之后,依次运行
sudo yarn
sudo yarn build
cd packages/diagrams-demo-gallery && sudo yarn run start
- 如果出现
targets must start with "./"
,则参考#646。
2. 集成antlr4到react-diagram
- 可以观察到
react-diagram
是由typescript书写的,所以我们需要让antlr形成typescript的API,刚好有一个antlr4ts库可以完成这个工作。
- 我们在
react-diagram
的根目录运行sudo yarn add antlr4ts
和sudo yarn add -D antlr4ts-cli
,如果报错出现了workspace
的字样,则在add
的后面加上-W
。
- 然后放入你的
.g4
文件,g4
文件规定了你的编程语言的语法规则,此处的文件为ReCol.g4
。
# ReCol.g4
grammar ReCol;
input: NEWLINE* name NEWLINE* entityDecl NEWLINE* entityRule NEWLINE*;
name: NEWLINE* 'regulation' ':' ID NEWLINE*;
/* =========================entityDecl==================================== */
entityDecl: 'Entities' NEWLINE* '{' obj* '}'
;
obj: NEWLINE* ID '{' obj* '}' NEWLINE* #newObject
| NEWLINE* ID NEWLINE* #newAttribute
| NEWLINE* ID ':' value=(DECIMAL|BOOL|STRING) NEWLINE* #newValue
;
/* =========================entityRule================================== */
entityRule: 'Rules' NEWLINE* '{' actionStat* '}' NEWLINE*;
actionStat: NEWLINE* ID('.'ID)* '=' NEWLINE*'[' expr ']' NEWLINE* # assignValue
| NEWLINE* ID('.'ID)* '=' NEWLINE*'[' rulerExpr ']' NEWLINE* # assignRuler
;
rulerExpr: rulerExpr '&' rulerExpr # AndRule
| rulerExpr 'AND' rulerExpr # AndRule
| rulerExpr '|' rulerExpr # OrRule
| rulerExpr 'OR' rulerExpr # OrRule
| rulerExpr '^' rulerExpr # XorRule
| rulerExpr 'XOR' rulerExpr # XorRule
| '!' rulerExpr # NotRule
| 'NOT' rulerExpr # NotRule
| rulerExpr 'Follow' rulerExpr # FollowRule
| rulerExpr 'Until' rulerExpr # UntilRule
| '(' rulerExpr ')' # ParentsRule
| boolexpr # ExprRule
;
/* =================================================================== */
boolexpr: expr '>' expr #checkLarger
| expr 'GT' expr #checkLarger
| expr '>=' expr #checkGE
| expr 'GE' expr #checkGE
| expr '<' expr #checkLess
| expr 'LT' expr #checkLess
| expr '<=' expr #checkLE
| expr 'LE' expr #checkLE
| expr '==' expr #checkequal
| expr 'EQ' expr #checkequal
| expr '!=' expr #checkNE
| expr 'NE' expr #checkNE
| expr #checkBoolExpr
;
expr:expr op=('*'|'/'|'+'|'-') expr # calNum
| expr op=('|'|'&') expr # calBool
| '!' expr # negate
| ID('.'ID)* # id
| DECIMAL # Decimal
| BOOL # bool
| STRING # string
| '(' expr ')' # parens
;
/* =================================================================== */
BOOL: 'True' | 'False';
STRING: '"' ~('"')* '"';
NEWLINE:'\r'? '\n' ;
WS : [ \t]+ -> skip ;
COMMENT : '/*' .*? '*/' -> skip;
LINE_COMMENT : '//' ~[\r\n]* -> skip;
PYTHON_COMMENT : '#' ~[\r\n]* -> skip;
MUL : '*' ;
DIV : '/' ;
ADD : '+' ;
SUB : '-' ;
AND : '&' ;
OR : '|' ;
NOT : '!' ;
GT : '>' ;
GE : '>=';
LT : '<' ;
LE : '<=';
EQ : '==';
NE : '!=';
DECIMAL : '-'? [0-9]+ ( '.' [0-9]+ )? ;
ID : [a-zA-Z_][a-zA-Z_0-9]* ;
- 然后再
diagrams-demo-gallery
下的packages.json
中添加"antlr4ts": "antlr4ts -visitor g4_script/ReCol.g4"
。
- 修改
demo/demo-serializing/index.tsx
import createEngine, {
DiagramModel, DefaultNodeModel } from '@projectstorm/react-diagrams';
import * as React from 'react';
import {
DemoButton, DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget';
import {
action } from '@storybook/addon-actions';
import * as beautify from 'json-beautify';
import {
CanvasWidget } from '@projectstorm/react-canvas-core';
import {
DemoCanvasWidget } from '../helpers/DemoCanvasWidget';
import RuleParser from './RuleParser';
export default () => {
var engine = createEngine();
var model = new DiagramModel();
var node1 = new DefaultNodeModel('Node 1', 'rgb(0,192,255)');
var port1 = node1.addOutPort('Out');
node1.setPosition(100, 100);
var node2 = new DefaultNodeModel('Node 2', 'rgb(192,255,0)');
var port2 = node2.addInPort('In');
node2.setPosition(400, 100);
var link1 = port1.link(port2);
model.addAll(node1, node2, link1);
engine.setModel(model);
var str = JSON.stringify(model.serialize());
var test = RuleParser;
console.log(test());
var model2 = new DiagramModel();
model2.deserializeModel(JSON.parse(str), engine);
engine.setModel(model2);
return (
<DemoWorkspaceWidget
buttons={
<DemoButton
onClick={
() => {
action('Serialized Graph')(beautify(model2.serialize(), null, 2, 80));
}}>
Serialize Graph
</DemoButton>
}>
<DemoCanvasWidget>
<CanvasWidget engine={
engine} />
</DemoCanvasWidget>
</DemoWorkspaceWidget>
);
};
demo/demo-serializing
中添加文件RuleParser.tsx
,并把你要处理的代码文件,如hello_world.rule
放入ReCol文件夹。注:其他文件都是后来自动生成的。
//hello_world.rule
regulation: HELLOWORLD
Entities { // DefineRegulatoryEntities
Asset {
id //uniqueid
name
value
}
}
Rules { // defineregulatoryrules
Rule1=[Asset.value>=100]
}
import {
ANTLRInputStream, CommonTokenStream } from 'antlr4ts';
import {
ReColLexer} from '../../g4_script/ReColLexer'
import {
ReColParser} from '../../g4_script/ReColParser'
export default () => {
let inputStream = new ANTLRInputStream("regulation: HELLOWORLD\nEntities {\nAsset {\n\t\tid\n\t\tname\n\t\tvalue\n\t}\n}\nRules {\n\tRule1=[Asset.value>=100]\n}");
let lexer = new ReColLexer(inputStream);
let tokenStream = new CommonTokenStream(lexer);
let parser = new ReColParser(tokenStream);
let tree = parser.input();
return tree;
};
- 最后命令行输出的结果: