I would like to create a grammar for the Conventional Commits spec and I would love to hear any feedback for what I wrote. The spec has some ambiguities, I think, hence my usage of "island grammars" to avoid writing predicates and such, which would limit the parser to a certain language target.
An example of a conventional commit message the parser should be able to handle
fix(some_module): this is a commit description
Some more in-depth description of what was fixed. This
can be a multi-line text, not only a one-liner.
Signed-off: [email protected]
Another-Key: another value
Some-Other-Key: some other value
The lexer:
lexer grammar ConventionalCommitLexer;
options { caseInsensitive = true; }
tokens { TEXT }
WS: [ \t]+ -> skip;
NEWLINE: ('\r')? '\n';
LPAREN: '(' -> mode(Scope);
RPAREN: ')';
SEMICOLON: ':' WS*-> mode(Description);
OTHER: 'other';
FEAT: 'feat';
FIX: 'fix';
DOCS: 'docs';
STYLE: 'style';
REFACTOR: 'refactor';
PERF: 'perf';
TEST: 'test';
CHORE: 'chore';
BUILD: 'build';
CI: 'ci';
BREAKING: 'breaking';
SECURITY: 'security';
REVERT: 'revert';
CONFIG: 'config';
UPGRADE: 'upgrade';
DOWNGRADE: 'downgrade';
PIN: 'pin';
IDENTIFIER: [a-z][a-z0-9_-]*;
mode Scope;
WS_SCOPE: [ \t] -> skip;
SCOPE: IDENTIFIER -> type(IDENTIFIER);
END_OF_SCOPE: RPAREN -> type(RPAREN), mode(DEFAULT_MODE);
mode Description;
DESCRIPTION: ~[\n]+ -> type(TEXT);
END_OF_TEXT: (WS | NEWLINE)+ -> mode(Body), type(NEWLINE);
mode Body;
END_OF_BODY: (WS | NEWLINE)+ -> mode(Footer), type(NEWLINE);
BODY: (SINGLE_LINE | MULTI_LINE) -> type(TEXT);
SINGLE_LINE
: ~[\n]+
;
MULTI_LINE
: (SINGLE_LINE | (~[\n]+ NEWLINE))+ -> mode(Footer)
;
mode Footer;
WS_FOOTER: ' ' -> skip;
KEY: IDENTIFIER -> type(IDENTIFIER);
SEPARATOR: WS_FOOTER* SEMICOLON WS_FOOTER* -> type(SEMICOLON), mode(FooterValue);
mode FooterValue;
NEWLINE_FOOTER: NEWLINE -> type(NEWLINE), mode(Footer);
VALUE: ~[\n]+ -> type(TEXT);
The parser:
parser grammar ConventionalCommitParser;
options { tokenVocab = ConventionalCommitLexer; }
type: (OTHER
| FEAT
| FIX
| DOCS
| STYLE
| REFACTOR
| PERF
| TEST
| CHORE
| BUILD
| CI
| BREAKING
| SECURITY
| REVERT
| CONFIG
| UPGRADE
| DOWNGRADE
| PIN) #RecognizedType
| IDENTIFIER #OtherType
;
footerKeyValue: key = IDENTIFIER SEMICOLON value = TEXT NEWLINE?;
commitMessage: type LPAREN scope = IDENTIFIER RPAREN SEMICOLON description = TEXT (NEWLINE body = TEXT)? (NEWLINE values += footerKeyValue+)? EOF;
fragment
more. Lexical symbols included in other lexical symbols should generally befragment
s, if at all possible. \$\endgroup\$