groowt/web-view-components-compiler/src/main/antlr/WebViewComponentsLexerBase.g4
2024-06-13 20:01:30 +02:00

765 lines
18 KiB
ANTLR

lexer grammar WebViewComponentsLexerBase;
import LexerFragments;
options {
superClass = AbstractWebViewComponentsLexer;
}
tokens {
PreambleBreak,
GroovyCode,
GStringAttrValueEnd,
JStringAttrValueEnd,
ClosureAttrValueEnd,
DollarScriptletClose,
Nlws,
ErrorChar
}
channels {
ERROR
}
@header {
import java.util.Set;
import groowt.view.component.web.WebViewComponentBugError;
import static groowt.view.component.web.antlr.LexerSemanticPredicates.*;
}
@members {
public static final Set<Integer> GroovyTokens = Set.of(
GroovyCodeChars,
LeftParenthesis,
RightParenthesis,
LeftCurly,
RightCurly,
LineCommentStart,
StarCommentStart,
JStringStart,
GStringStart,
TripleJStringStart,
TripleGStringStart,
ParenthesesSlashyStringStart,
DollarSlashyStringStart,
LineCommentText,
LineCommentEnd,
StarCommentChars,
StarCommentEnd,
JStringText,
JStringEnd,
GStringText,
GStringDollarValueStart,
GStringClosureStart,
GStringEnd,
GStringIdentifier,
GStringDot,
GStringPathEnd,
GStringIdentifierStartChar,
GStringIdentifierChar,
TripleJStringContent,
TripleJStringEnd,
TripleGStringDollarValueStart,
TripleGStringClosureStart,
TripleGStringText,
TripleGStringEnd,
ParenthesesSlashyStringText,
ParenthesesSlashyStringDollarValueStart,
ParenthesesSlashyStringClosureStart,
ParenthesesSlashyStringEnd,
DollarSlashyStringText,
DollarSlashyStringDollarValueStart,
DollarSlashyStringClosureStart,
DollarSlashyStringEnd
);
public static final Set<Integer> GStringParts = Set.of(
GStringDollarValueStart,
GStringClosureStart,
GStringIdentifier,
GStringDot,
GStringPathEnd,
GStringIdentifierStartChar,
GStringIdentifierChar,
TripleGStringDollarValueStart,
TripleGStringClosureStart,
ParenthesesSlashyStringDollarValueStart,
ParenthesesSlashyStringClosureStart,
DollarSlashyStringDollarValueStart,
DollarSlashyStringClosureStart
);
public WebViewComponentsLexerBase() {}
private void onPreambleClose() {
this.setType(PreambleBreak);
this.exitPreamble();
this.mode(MAIN);
}
private void onGStringClosure() {
this.curlies.push(this::popMode); // onPop
this.curlies.increment(); // after, curlies.currentCount == 1
this.pushMode(GROOVY_CODE);
}
@Override
protected void enterConstructor() {
super.enterConstructor(); // setup state
this.pushMode(GROOVY_CODE);
}
@Override
protected boolean inAttrComponent() {
return this.peekMode(1) == COMPONENT_ATTR_VALUE;
}
}
// ----------------------------------------
// DEFAULT_MODE
PreambleOpen
: THREE_DASH ( NL | WS+ )? { this.enterPreamble(); }
-> type(PreambleBreak), pushMode(GROOVY_CODE)
;
NotPreambleOpen
: .
{
this.rollbackOne();
this.setCanPreamble(false);
} -> skip, mode(MAIN)
;
// ----------------------------------------
mode MAIN;
ComponentOpen
: LT { !isAnyOf(this.getNextChar(), '/', '>') }? -> pushMode(TAG_START)
;
ClosingComponentOpen
: LT FS { !this.isNext('>') }? -> pushMode(TAG_START)
;
FragmentOpen
: LT GT
;
FragmentClose
: LT FS GT
;
EqualsScriptletOpen
: LT PERCENT EQ -> pushMode(GROOVY_CODE)
;
PlainScriptletOpen
: LT PERCENT -> pushMode(GROOVY_CODE)
;
DollarScriptletOpen
: DOLLAR LEFT_CURLY {
this.curlies.push(() -> {
this.setType(DollarScriptletClose);
this.popMode();
});
this.curlies.increment();
this.pushMode(GROOVY_CODE);
}
;
DollarReferenceStart
: DOLLAR { isGStringIdentifierStartChar(this.getNextChar()) }? -> pushMode(IN_G_STRING_PATH)
;
QuestionTagOpen
: LT QUESTION
;
QuestionTagClose
: QUESTION GT
;
HtmlCommentOpen
: LT BANG TWO_DASH
;
HtmlCommentClose
: TWO_DASH GT
;
RawText
: ( ~[-<$?]
| MINUS { !this.isNext("->") }?
| LT { canFollowLessThan(this.getNextCharAsString()) }?
| LT BANG { !this.isNext("--") }?
| DOLLAR { !(this.isNext('{') || isIdentifierStartChar(this.getNextChar())) }?
| QUESTION { !this.isNext('>') }?
)+
;
MainError
: . -> type(ErrorChar), channel(ERROR)
;
// ----------------------------------------
mode TAG_START;
TypedIdentifier
: ( PackageIdentifier DOT )* ClassIdentifier ( DOT ClassIdentifier )* -> mode(IN_TAG)
;
fragment
PackageIdentifier
: PackageIdentifierStartChar PackageIdentifierChar*
;
fragment
PackageIdentifierStartChar
: [\p{Ll}]
;
fragment
PackageIdentifierChar
: [\p{L}_0-9]
;
fragment
ClassIdentifier
: ClassIdentifierStartChar ClassIdentifierChar*
;
fragment
ClassIdentifierStartChar
: [\p{Lu}]
;
fragment
ClassIdentifierChar
: [\p{L}_0-9]
;
StringIdentifier
: StringIdentifierStartChar StringIdentifierChar* -> mode(IN_TAG)
;
fragment
StringIdentifierStartChar
: [\p{Ll}]
;
fragment
StringIdentifierChar
: [-_0-9\p{L}]
;
TagStartNlws
: NLWS+ -> type(Nlws), channel(HIDDEN)
;
TagStartError
: . -> type(ErrorChar), channel(ERROR)
;
// ----------------------------------------
mode IN_TAG;
ComponentClose
: GT
{
if (this.inAttrComponent() && this.isAttrComponentFinished()) {
this.popMode();
this.popMode();
} else {
this.popMode();
}
}
;
ComponentSelfClose
: FS GT
{
if (this.inAttrComponent()) {
this.exitAttrComponent();
if (this.isAttrComponentFinished()) {
this.popMode(); // Do it two times total
}
}
this.popMode();
}
;
ConstructorOpen
: LP { this.enterConstructor(); }
;
AttributeIdentifier
: AttributeIdentifierStartChar AttributeIdentifierChar*
;
fragment
AttributeIdentifierStartChar
: [\p{L}_$]
;
fragment
AttributeIdentifierChar
: [-\p{L}_$0-9]
;
Equals
: EQ
;
GStringAttrValueStart
: DQ -> pushMode(IN_G_STRING)
;
JStringAttrValueStart
: SQ -> pushMode(IN_J_STRING)
;
ClosureAttrValueStart
: LEFT_CURLY { !this.isNextIgnoreNlws('<') }?
{
this.curlies.push(() -> {
this.setType(ClosureAttrValueEnd);
this.popMode();
});
this.curlies.increment();
this.pushMode(GROOVY_CODE);
}
;
ComponentAttrValueStart
: LEFT_CURLY InTagNlws? { this.isNext('<') }?
{
this.pushAttrComponent();
this.pushMode(COMPONENT_ATTR_VALUE);
}
;
ComponentAttrValueEnd
: InTagNlws? RIGHT_CURLY { this.popAttrComponent(); }
;
InTagNlws
: NLWS+ -> type(Nlws), channel(HIDDEN)
;
TagError
: . -> channel(ERROR)
;
// ----------------------------------------
mode COMPONENT_ATTR_VALUE;
AttrComponentOpen
: LT { !isAnyOf(this.getNextChar(), '/', '>') }?
{
this.enterAttrComponent();
} -> type(ComponentOpen), pushMode(TAG_START)
;
AttrClosingComponentOpen
: LT FS { !this.isNext('>') }?
{
this.exitAttrComponent();
} -> type(ClosingComponentOpen), pushMode(TAG_START)
;
AttrFragmentOpen
: LT GT
{
this.enterAttrComponent();
} -> type(FragmentOpen)
;
AttrFragmentClose
: LT FS GT {
this.exitAttrComponent();
} -> type(FragmentClose), popMode
;
AttrEqualsScriptletOpen
: LT PERCENT EQ -> type(EqualsScriptletOpen), pushMode(GROOVY_CODE)
;
AttrPlainScriptletOpen
: LT PERCENT -> type(PlainScriptletOpen), pushMode(GROOVY_CODE)
;
AttrDollarScriptletOpen
: DOLLAR LEFT_CURLY {
this.curlies.push(() -> {
this.setType(DollarScriptletClose);
this.popMode();
});
this.curlies.increment();
this.setType(DollarScriptletOpen);
this.pushMode(GROOVY_CODE);
}
;
AttrDollarReferenceStart
: DOLLAR { isGStringIdentifierStartChar(this.getNextChar()) }?
-> type(DollarReferenceStart), pushMode(IN_G_STRING_PATH)
;
AttrQuestionTagOpen
: LT QUESTION -> type(QuestionTagOpen)
;
AttrQuestionTagClose
: QUESTION GT -> type(QuestionTagClose)
;
AttrHtmlCommentOpen
: LT BANG TWO_DASH -> type(HtmlCommentOpen)
;
AttrHtmlCommentClose
: TWO_DASH GT -> type(HtmlCommentClose)
;
AttrRawText
: ( ~[-<$?]
| MINUS { !this.isNext("->") }?
| LT { canFollowLessThan(this.getNextCharAsString()) }?
| LT BANG { !this.isNext("--") }?
| DOLLAR { !(this.isNext('{') || isIdentifierStartChar(this.getNextChar())) }?
| QUESTION { !this.isNext('>') }?
)+ -> type(RawText)
;
AttrError
: . -> type(ErrorChar), channel(ERROR)
;
// ----------------------------------------
mode GROOVY_CODE;
PreambleClose
: THREE_DASH { this.inPreamble() && this.getCharPositionInLine() == 3 }? WS* NL? { this.onPreambleClose(); }
;
ScriptletClose
: PERCENT GT -> popMode
;
GroovyCodeChars
: ( ~[-/$%(){}'"]
| MINUS { !(this.getCharPositionInLine() == 1 && this.isNext("--")) }?
| FS { !isAnyOf(this.getNextChar(), '/', '*') }?
| DOLLAR { !this.isNext('/') }?
| PERCENT { !this.isNext('>') }?
)+
;
LeftParenthesis
: LP { !this.isNext('/') }?
{
if (this.parentheses.isCounting()) {
this.parentheses.increment();
}
}
;
ConstructorClose
: RP { this.canExitConstructor() }? { this.exitConstructor(); }
;
RightParenthesis
: RP { !this.inConstructor() }?
{
if (this.parentheses.isCounting()) {
this.parentheses.decrement();
}
}
;
LeftCurly
: LEFT_CURLY
{
if (this.curlies.isCounting()) {
this.curlies.increment();
}
}
;
RightCurly
: RIGHT_CURLY
{
if (this.curlies.isCounting()) {
if (this.curlies.isLast()) {
this.curlies.pop(); // calls this.pop() in onPop
} else {
this.curlies.decrement();
}
}
}
;
LineCommentStart
: FS FS -> pushMode(IN_LINE_COMMENT)
;
StarCommentStart
: FS STAR -> pushMode(IN_STAR_COMMENT)
;
JStringStart
: SQ { canFollowJStringOpening(this.getNextCharsAsString(2)) }? -> pushMode(IN_J_STRING)
;
GStringStart
: DQ { canFollowGStringOpening(this.getNextCharsAsString(2)) }? -> pushMode(IN_G_STRING)
;
TripleJStringStart
: SQ SQ SQ -> pushMode(IN_TRIPLE_J_STRING)
;
TripleGStringStart
: DQ DQ DQ -> pushMode(IN_TRIPLE_G_STRING)
;
ParenthesesSlashyStringStart
: LP FS { !isAnyOf(this.getNextChar(), '/', '*') }? -> pushMode(IN_PARENTHESES_SLASHY_STRING)
;
DollarSlashyStringStart
: DOLLAR FS -> pushMode(IN_DOLLAR_SLASHY_STRING)
;
// ----------------------------------------
mode IN_LINE_COMMENT;
LineCommentText
: ~[\n\r]+
;
LineCommentEnd
: NL -> popMode
;
// ----------------------------------------
mode IN_STAR_COMMENT;
StarCommentChars
: ~'*' | ( '*' { !this.isNext('/') }? )
;
StarCommentEnd
: STAR FS -> popMode
;
// ----------------------------------------
mode IN_J_STRING;
JStringText
: ( ~[\n\r'] | BS SQ )+
;
JStringEnd
: SQ
{
if (this.peekMode(1) == IN_TAG) {
this.setType(JStringAttrValueEnd);
}
this.popMode();
}
;
// ----------------------------------------
mode IN_G_STRING;
GStringText
: ( ~[\n\r"$]
| BS DQ
| BS DOLLAR
| DOLLAR { !isGStringIdentifierStartChar(this.getNextChar()) }?
)+
;
GStringDollarValueStart
: DOLLAR { !this.isNext('{') && isGStringIdentifierStartChar(this.getNextChar()) }? -> pushMode(IN_G_STRING_PATH)
;
GStringClosureStart
: DOLLAR LEFT_CURLY { this.onGStringClosure(); }
;
GStringEnd
: DQ
{
if (this.peekMode(1) == IN_TAG) {
this.setType(GStringAttrValueEnd);
}
this.popMode();
}
;
// ----------------------------------------
mode IN_G_STRING_PATH;
GStringIdentifier
: GStringIdentifierStartChar GStringIdentifierChar*
;
GStringDot
: DOT { isGStringIdentifierStartChar(this.getNextChar()) }?
;
GStringPathEnd
: ~[."/] { !isGStringIdentifierChar(this.getCurrentChar()) }?
{
final var current = this.getCurrentChar();
final GStringPathEndSpec endSpec = switch (this.peekMode(1)) {
case IN_G_STRING -> {
if (current == '"') {
yield new StringClosingEndSpec(GStringEnd, 2);
} else {
yield new StringContinueEndSpec();
}
}
case IN_TRIPLE_G_STRING -> {
if (current == '"' && this.isNext("\"\"")) {
yield new StringClosingEndSpec(TripleGStringEnd, 2);
} else {
yield new StringContinueEndSpec();
}
}
case IN_PARENTHESES_SLASHY_STRING -> {
if (current == '/' && this.isNext(')')) {
yield new StringClosingEndSpec(ParenthesesSlashyStringEnd, 2);
} else {
yield new StringContinueEndSpec();
}
}
case IN_DOLLAR_SLASHY_STRING -> {
if (current == '/' && this.isNext('$')) {
yield new StringClosingEndSpec(DollarSlashyStringEnd, 2);
} else {
yield new StringContinueEndSpec();
}
}
case IN_TAG -> {
if (current == '"') {
yield new StringClosingEndSpec(GStringAttrValueEnd, 2);
} else {
yield new StringContinueEndSpec();
}
}
case COMPONENT_ATTR_VALUE -> new StringContinueEndSpec();
case MAIN -> new StringContinueEndSpec();
default -> throw new IllegalStateException(
"not a valid gStringPath context: " + this.getModeName(this.peekMode(1))
);
};
switch (endSpec) {
case StringContinueEndSpec ignored -> {
this.popMode();
this.rollbackOne(true);
this.skip();
}
case StringClosingEndSpec closingEndSpec -> {
this.setType(closingEndSpec.type());
for (int i = 0; i < closingEndSpec.popCount(); i++) {
this.popMode();
}
}
}
}
;
GStringIdentifierStartChar
: ~'.' { isGStringIdentifierStartChar(this.getCurrentChar()) }?
;
GStringIdentifierChar
: ~'.' { isGStringIdentifierChar(this.getCurrentChar()) }?
;
// ----------------------------------------
mode IN_TRIPLE_J_STRING;
TripleJStringContent
: ( ~[']
| SQ { !(this.isNext("''") && this._input.LA(3) != '\'') }?
)+
;
TripleJStringEnd
: SQ SQ SQ { !this.isNext('\'') }? -> popMode
;
// ----------------------------------------
mode IN_TRIPLE_G_STRING;
TripleGStringDollarValueStart
: DOLLAR { isGStringIdentifierStartChar(this.getNextChar()) }? -> pushMode(IN_G_STRING_PATH)
;
TripleGStringClosureStart
: DOLLAR LEFT_CURLY { this.onGStringClosure(); }
;
TripleGStringText
: ( ~["$]
| DQ { !(this.isNext("\"\"") && this._input.LA(3) != '"') }?
| BS DOLLAR
| DOLLAR { !isGStringIdentifierStartChar(this.getNextChar()) }?
)+
;
TripleGStringEnd
: DQ DQ DQ { !this.isNext('"') }? -> popMode
;
// ----------------------------------------
mode IN_PARENTHESES_SLASHY_STRING;
ParenthesesSlashyStringText
: ( ~'/' | BS FS )+
;
ParenthesesSlashyStringDollarValueStart
: DOLLAR { isGStringIdentifierStartChar(this.getNextChar()) }? -> pushMode(IN_G_STRING_PATH)
;
ParenthesesSlashyStringClosureStart
: DOLLAR LEFT_CURLY { this.onGStringClosure(); }
;
ParenthesesSlashyStringEnd
: FS RP -> popMode
;
// ----------------------------------------
mode IN_DOLLAR_SLASHY_STRING;
DollarSlashyStringText
: ( ~[$/]
| DOLLAR DOLLAR
| DOLLAR { !isGStringIdentifierStartChar(this.getNextChar()) }?
| FS { !this.isNext('$') }?
)+
;
DollarSlashyStringDollarValueStart
: DOLLAR { isGStringIdentifierStartChar(this.getNextChar()) }?
-> pushMode(IN_G_STRING_PATH)
;
DollarSlashyStringClosureStart
: DOLLAR LEFT_CURLY { this.onGStringClosure(); }
;
DollarSlashyStringEnd
: FS DOLLAR { !this.isNext('$') }? -> popMode
;