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 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 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 ;