Поэтому я работаю с микросервисами. Из-за этого иногда я пишу коды, которые действуют как компоновщик, вызывающий один или несколько микросервисов и обрабатывающий результаты. Я также должен создать диаграмму последовательности, чтобы передать ее разработчику внешнего интерфейса, чтобы он мог понять поток вызовов.

Однажды мой коллега показывает, как он создал свою диаграмму последовательности. Он использует PlantUML, где ему просто нужно написать какой-то DSL, а затем редактор напрямую отобразит сгенерированную диаграмму последовательности в реальном времени. Я подумал, кажется, я могу использовать это, чтобы также генерировать коды композитора вместе с конфигурациями, а также с интеграционными тестами и, возможно, модульными тестами.

Для этого мне нужно иметь грамматику PlantUML. Я искал в сети, и я не могу найти. PlantUML имеет только ссылку на язык, и они анализируют язык без грамматики, а только с использованием регулярных выражений. Если я хочу скомпилировать язык в свои собственные коды, будет проще, если я начну с грамматики.

Затем я исследовал, какой инструментарий парсера удобен для использования в мире `nodejs`. И я нашел nearleyjs, и кто-то уже построил онлайн-площадку здесь, так что я могу начать взламывать напрямую.

Через некоторое время я создал то, что кажется работающей грамматикой PlantUML. Я поставил его по сути здесь. Или, если вы хотите увидеть это напрямую, я вставил его ниже:

statement -> element_expr:*  {% id %}
	| null {% id %}

element_expr -> actor_expr {% id %}
	| boundary_expr  {% id %}
	| call_expr {% id %}
	| participant_expr {% id %}


actor_expr -> ACTOR {% d => d[0] %}
	| ACTOR _ ELEMENT_COLOR {% d => [ d[0][0], d[0][1], d[2] ] %}
	| ACTOR _ ELEMENT_ORDER {% d => [ d[0][0], d[0][1], d[2] ] %}
	| ACTOR _ ELEMENT_COLOR _ ELEMENT_ORDER
	| ACTOR _ ELEMENT_ORDER _ ELEMENT_COLOR 
	
boundary_expr -> BOUNDARY  {% d => d[0] %}
	| BOUNDARY _ ELEMENT_COLOR {% d => [ d[0][0], d[0][1], d[2] ] %}
	| BOUNDARY _ ELEMENT_ORDER {% d => [ d[0][0], d[0][1], d[2] ] %}
	| BOUNDARY _ ELEMENT_COLOR _ ELEMENT_ORDER
	| BOUNDARY _ ELEMENT_ORDER _ ELEMENT_COLOR 

participant_expr -> PARTICIPANT {% d => d[0] %}
	| PARTICIPANT _ ELEMENT_COLOR {% d => [ d[0][0], d[0][1], d[2] ] %}
	| PARTICIPANT _ ELEMENT_ORDER {% d => [ d[0][0], d[0][1], d[2] ] %}
	| PARTICIPANT _ ELEMENT_COLOR _ ELEMENT_ORDER
	| PARTICIPANT _ ELEMENT_ORDER _ ELEMENT_COLOR 

call_expr -> CALL {% d => d[0] %}

id -> STRING  {% d => ["IDENTIFIER", d[0][1] ] %}
	| dqstring  {% d => ["IDENTIFIER", d[0] ] %}
integer -> INT {% id %}

CALL_ARROW -> CALL_ARROW_BODY CALL_ARROW_HEAD CALL_ARROW_HEAD_MODIFIER
		| CALL_ARROW_BODY CALL_ARROW_HEAD
		| CALL_ARROW_HEAD_MODIFIER CALL_ARROW_HEAD CALL_ARROW_BODY
		
		
		
CALL_ARROW_BODY -> "-"
			| "--"
			| "->"
			| "<-"
			
CALL_ARROW_HEAD -> ">"
			| "<"
			| "\\"
			| "\\\\"
			| "/"
			
CALL_ARROW_HEAD_MODIFIER -> "x"
			| ">"
			| "<"
			| "\\"
			| "/"
			| "o"
			
CALL -> id _ CALL_ARROW _ id _ ":" _ MESSAGE_NAME _ {% d => ["CALL", d[0], d[4], d[7]] %}
ACTOR -> "actor" _ id _ {% d => ["ACTOR", d[2] ] %}
BOUNDARY -> "boundary" _ id _ {% d => ["BOUNDARY", d[2] ] %}
PARTICIPANT -> "participant" _ id _ {% d => ["PARTICIPANT", d[2] ] %}
ELEMENT_ORDER -> "order" _ INT _ {% d => ["ELEMENT_ORDER", d[2] ] %}
MESSAGE_NAME -> dstrchar:* {% d => ["MESSAGE_NAME", d[0].join('')] %}		
ELEMENT_COLOR -> "#" STRING _ {% d => ["ELEMENT_COLOR", d[1][1] ] %}

INT -> [0-9]:+ {% d => ["INT", d[0][0]] %}
STRING -> [a-zA-Z]:+ {% d => ["STRING", d[0].join('')] %}
STRINGW -> [a-zA-Z ]:+ {% d => ["STRING", d[0].join('')] %}
WHITESPACE -> _

_  -> wschar:* {% function(d) {return null;} %}
__ -> wschar:+ {% function(d) {return null;} %}

wschar -> [ \t\n\v\f] {% id %}

# Matches various kinds of string literals

# Double-quoted string
dqstring -> "\"" dstrchar:* "\"" {% function(d) {return d[1].join(""); } %}
sqstring -> "'"  sstrchar:* "'"  {% function(d) {return d[1].join(""); } %}
btstring -> "`"  [^`]:*    "`"  {% function(d) {return d[1].join(""); } %}

dstrchar -> [^\\"\n] {% id %}
    | "\\" strescape {%
    function(d) {
        return JSON.parse("\""+d.join("")+"\"");
    }
%}

sstrchar -> [^\\'\n] {% id %}
    | "\\" strescape
        {% function(d) { return JSON.parse("\""+d.join("")+"\""); } %}
    | "\\'"
        {% function(d) {return "'"; } %}

strescape -> ["\\/bfnrt] {% id %}
    | "u" [a-fA-F0-9] [a-fA-F0-9] [a-fA-F0-9] [a-fA-F0-9] {%
    function(d) {
        return d.join("");
    }
%}

Отказ от ответственности, я почти не анализировал грамматику, я использовал игровую площадку только для проверки грамматики. Вот некоторые из кодов PlantUML, которые я использую для тестирования:

autonumber 40 20 "<asd>"
actor    "new Aliceasdadad()" order 20 #yellow
boundary "Bob\nvery long" #yellow order 20
Alice -[#red]> Bob:  Authentication\n request 
participant Juki #yellow order 20
participant rob
Bob ->x Alice: asdad
Bob -> Alice: adasd
Bob <->o Lice: adsad