Lexer identifier refactorings and basic lexer/parser error listeners.

This commit is contained in:
JesseBrault0709 2024-05-21 07:55:39 +02:00
parent e747ac3c32
commit 80b01ec27a
39 changed files with 595 additions and 163 deletions

View File

@ -112,11 +112,11 @@ PreambleOpen
;
ComponentOpen
: LT -> pushMode(IN_TAG)
: LT -> pushMode(TAG_START)
;
ClosingComponentOpen
: LT FS -> pushMode(IN_TAG)
: LT FS -> pushMode(TAG_START)
;
EqualsScriptletOpen
@ -159,34 +159,87 @@ RawText
;
// ----------------------------------------
mode IN_TAG;
mode TAG_START;
ComponentSelfClose
: FS GT -> popMode
FragmentClose
: GT -> popMode
;
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}]
;
// ----------------------------------------
mode IN_TAG;
ComponentClose
: GT -> popMode
;
Identifier
: IdentifierStartChar IdentifierChar*
;
IdentifierStartChar
: ~[.] { isIdentifierStartChar(this.getCurrentChar()) }?
;
IdentifierChar
: ~[.] { isIdentifierChar(this.getCurrentChar()) }?
ComponentSelfClose
: FS GT -> popMode
;
ConstructorOpen
: LP { this.enterConstructor(); }
;
Dot
: DOT
AttributeIdentifier
: AttributeIdentifierStartChar AttributeIdentifierChar*
;
fragment
AttributeIdentifierStartChar
: [\p{L}_$]
;
fragment
AttributeIdentifierChar
: [\p{L}_$0-9]
;
Equals

View File

@ -67,7 +67,7 @@ componentArgs
;
componentType
: Identifier ( Dot Identifier )*
: TypedIdentifier | StringIdentifier
;
componentConstructor
@ -79,11 +79,11 @@ attr
;
keyValueAttr
: Identifier Equals value
: AttributeIdentifier Equals value
;
booleanAttr
: Identifier
: AttributeIdentifier
;
value

View File

@ -1,17 +1,25 @@
@file:JvmName("MismatchedComponentTypeAnalysis")
package groowt.view.component.web.analysis
import groowt.view.component.web.WebViewComponentBugError
import groowt.view.component.web.antlr.WebViewComponentsParser.ComponentTypeContext
import groowt.view.component.web.antlr.WebViewComponentsParser.ComponentWithChildrenContext
import groowt.view.component.web.util.SourcePosition
import org.antlr.v4.runtime.ParserRuleContext
import org.antlr.v4.runtime.Token
import org.antlr.v4.runtime.tree.ParseTree
import org.antlr.v4.runtime.tree.TerminalNode
private fun getIdentifiers(
componentTypeContext: ComponentTypeContext
): List<Token> = componentTypeContext.Identifier().map(TerminalNode::getSymbol)
private fun getIdentifiers(ctx: ComponentTypeContext): Token {
val typedIdentifier = ctx.TypedIdentifier()
if (typedIdentifier != null) {
return typedIdentifier.symbol
}
val stringIdentifier = ctx.StringIdentifier()
if (stringIdentifier != null) {
return stringIdentifier.symbol
}
throw WebViewComponentBugError("Could not determine identifier type: $ctx")
}
private fun getErrorMessage(
openType: ComponentTypeContext,
@ -20,20 +28,8 @@ private fun getErrorMessage(
"Found '${openType.text}' at ${SourcePosition.formatStartOfTokenLong(openType.start)} " +
"and '${closingType.text}' at ${SourcePosition.formatStartOfTokenLong(closingType.start)}."
private fun test(
openIdentifiers: List<Token>,
closingIdentifiers: List<Token>
): Boolean {
if (openIdentifiers.size != closingIdentifiers.size) {
return false
}
openIdentifiers.zip(closingIdentifiers).forEach { (openIdentifier, closingIdentifier) ->
if (!openIdentifier.text.equals(closingIdentifier.text)) {
return false
}
}
return true
}
private fun test(openIdentifiers: Token, closingIdentifiers: Token): Boolean =
openIdentifiers.text.equals(closingIdentifiers.text)
private fun doCheck(tree: ParseTree, destination: MutableList<MismatchedComponentTypeError>) {
if (tree is ParserRuleContext) {
@ -43,9 +39,9 @@ private fun doCheck(tree: ParseTree, destination: MutableList<MismatchedComponen
if (tree is ComponentWithChildrenContext) {
val openType: ComponentTypeContext = tree.openComponent().componentArgs().componentType()
val closingType: ComponentTypeContext = tree.closingComponent().componentType()
val openTypeIdentifiers = getIdentifiers(openType)
val closingTypeIdentifiers = getIdentifiers(closingType)
if (!test(openTypeIdentifiers, closingTypeIdentifiers)) {
val openIdentifier = getIdentifiers(openType)
val closingIdentifier = getIdentifiers(closingType)
if (!test(openIdentifier, closingIdentifier)) {
destination.add(MismatchedComponentTypeError(tree, getErrorMessage(openType, closingType)))
}
}

View File

@ -0,0 +1,9 @@
package groowt.view.component.web.antlr
import groowt.view.component.web.util.SourcePosition
data class LexerError(val type: LexerErrorType, val sourcePosition: SourcePosition)
fun format(lexerError: LexerError): String {
return "At ${lexerError.sourcePosition.toStringLong()}: ${lexerError.type.message}"
}

View File

@ -0,0 +1,66 @@
package groowt.view.component.web.antlr
import groowt.view.component.web.util.SourcePosition
import org.antlr.v4.runtime.*
import org.antlr.v4.runtime.atn.ATNConfigSet
import org.antlr.v4.runtime.dfa.DFA
import java.util.*
class LexerErrorListener : ANTLRErrorListener {
private val errors: MutableList<LexerError> = ArrayList()
fun getErrors(): List<LexerError> = this.errors
override fun syntaxError(
recognizer: Recognizer<*, *>,
offendingSymbol: Any?,
line: Int,
charPositionInLine: Int,
msg: String,
e: RecognitionException
) {
if (e is LexerNoViableAltException) {
val sourcePosition = SourcePosition(line, charPositionInLine + 1)
val lexerError = LexerError(LexerErrorType.NO_VIABLE_ALTERNATIVE, sourcePosition)
errors.add(lexerError)
} else {
throw e
}
}
override fun reportAmbiguity(
recognizer: Parser?,
dfa: DFA?,
startIndex: Int,
stopIndex: Int,
exact: Boolean,
ambigAlts: BitSet?,
configs: ATNConfigSet?
) {
throw UnsupportedOperationException()
}
override fun reportAttemptingFullContext(
recognizer: Parser?,
dfa: DFA?,
startIndex: Int,
stopIndex: Int,
conflictingAlts: BitSet?,
configs: ATNConfigSet?
) {
throw UnsupportedOperationException()
}
override fun reportContextSensitivity(
recognizer: Parser?,
dfa: DFA?,
startIndex: Int,
stopIndex: Int,
prediction: Int,
configs: ATNConfigSet?
) {
throw UnsupportedOperationException()
}
}

View File

@ -0,0 +1,5 @@
package groowt.view.component.web.antlr
enum class LexerErrorType(val message: String) {
NO_VIABLE_ALTERNATIVE("No viable alternative.")
}

View File

@ -70,7 +70,7 @@ fun isIdentifierStartChar(c: Char): Boolean = Character.isJavaIdentifierStart(c)
fun isIdentifierStartChar(subject: Int) = isIdentifierStartChar(subject.toChar())
fun isIdentifierChar(c: Char): Boolean = Character.isJavaIdentifierPart(c)
fun isIdentifierChar(c: Char): Boolean = Character.isJavaIdentifierPart(c) || c == '-'
fun isIdentifierChar(subject: Int) = isIdentifierChar(subject.toChar())

View File

@ -4,4 +4,12 @@ package groowt.view.component.web.antlr
import org.antlr.v4.runtime.CharStream
import org.antlr.v4.runtime.Token
fun runLexerAllTokens(input: CharStream): List<Token> = WebViewComponentsLexer(input).allTokens
fun runLexerAllTokensRaw(input: CharStream): List<Token> = WebViewComponentsLexer(input).allTokens
fun runLexerAllTokens(input: CharStream, withEOF: Boolean = false): List<Token> {
val lexer = WebViewComponentsLexer(input)
val stream = WebViewComponentsTokenStream(lexer)
return if (withEOF) stream.getAllTokens() else stream.getAllTokens()
}
fun runLexerAllTokens(input: CharStream) = runLexerAllTokens(input, withEOF = false)

View File

@ -0,0 +1,50 @@
package groowt.view.component.web.antlr
import groowt.view.component.web.util.SourcePosition
import groowt.view.component.web.util.excerpt
import org.antlr.v4.runtime.ParserRuleContext
import org.antlr.v4.runtime.Token
open class ParserError(val type: ParserErrorType, val offending: Token, val context: ParserRuleContext)
class MismatchedInputParserError(
type: ParserErrorType,
offending: Token,
context: ParserRuleContext,
val expectedTokenTypes: Set<Int>
) : ParserError(type, offending, context)
fun format(error: ParserError): String {
val sb = StringBuilder()
val sourcePosition = SourcePosition.fromStartOfToken(error.offending)
sb.append("At ")
.append(sourcePosition.toStringLong())
.append(": ")
.append(error.type.message)
.append(" Offending token: ")
.append(formatTokenForError(error.offending))
.append(". ")
if (error is MismatchedInputParserError) {
sb.append("Expected any of: ")
.append(formatExpected(error.expectedTokenTypes))
.append(". ")
}
sb.append("(" + formatContext(error.context) + ").")
return sb.toString()
}
private fun formatTokenForError(token: Token): String {
return "'${token.text}' (${getTokenName(token)})"
}
private fun formatContext(context: ParserRuleContext): String {
val sb = StringBuilder()
sb.append("context: ${context.javaClass.simpleName}")
if (context.text.isNotEmpty()) {
sb.append(", text: ")
.append(escapeChars(excerpt(context.text)))
}
return sb.toString()
}
private fun formatExpected(expectedTokenTypes: Set<Int>): String = expectedTokenTypes.joinToString { getTokenName(it) }

View File

@ -0,0 +1,41 @@
package groowt.view.component.web.antlr
import org.antlr.v4.runtime.*
class ParserErrorListener : BaseErrorListener() {
private val errors: MutableList<ParserError> = ArrayList()
fun getErrors(): List<ParserError> = this.errors
override fun syntaxError(
recognizer: Recognizer<*, *>,
offendingSymbol: Any,
line: Int,
charPositionInLine: Int,
msg: String,
e: RecognitionException
) {
val parser = recognizer as WebViewComponentsParser
when (e) {
is NoViableAltException -> {
val error = ParserError(ParserErrorType.NO_VIABLE_ALTERNATIVE, e.offendingToken, parser.context)
errors.add(error)
}
is InputMismatchException -> {
val error = MismatchedInputParserError(
ParserErrorType.INPUT_MISMATCH,
e.offendingToken,
parser.context,
e.expectedTokens.toSet()
)
errors.add(error)
}
is FailedPredicateException -> {
val error = ParserError(ParserErrorType.FAILED_PREDICATE, e.offendingToken, parser.context)
errors.add(error)
}
}
}
}

View File

@ -0,0 +1,7 @@
package groowt.view.component.web.antlr
enum class ParserErrorType(val message: String) {
NO_VIABLE_ALTERNATIVE("No viable alternative."),
INPUT_MISMATCH("Input mismatch."),
FAILED_PREDICATE("Input failed predicate.")
}

View File

@ -38,9 +38,6 @@ fun parseCompilationUnit(charStream: CharStream): CompilationUnitParseResult {
return CompilationUnitParseResult(lexer, tokenStream, parser, cu)
}
fun parseCompilationUnit(tokenStream: TokenStream): CompilationUnitContext =
parse(tokenStream, WebViewComponentsParser::compilationUnit)
fun parseCompilationUnit(
tokenStream: TokenStream,
onResult: BiConsumer<CompilationUnitContext, WebViewComponentsParser>

View File

@ -3,17 +3,18 @@ package groowt.view.component.web.antlr
import groowt.view.component.web.antlr.WebViewComponentsLexer.GStringParts
import groowt.view.component.web.antlr.WebViewComponentsLexer.GroovyTokens
import groowt.view.component.web.util.SourcePosition
import org.antlr.v4.runtime.Token
fun isGroovyTokenType(token: Token) = isGroovyTokenType(token.type)
fun isGroovyTokenType(type: Int): Boolean = type in GroovyTokens
fun isGStringPart (token: Token) = isGStringPart(token.type)
fun isGStringPart(token: Token) = isGStringPart(token.type)
fun isGStringPart(type: Int): Boolean = type in GStringParts
fun getTokenName (token: Token) = getTokenName(token.type)
fun getTokenName(token: Token) = getTokenName(token.type)
fun getTokenName(type: Int): String = WebViewComponentsLexer.VOCABULARY.getDisplayName(type)
@ -25,15 +26,17 @@ fun interface TokenTextFormatter {
}
fun formatToken(token: Token, textFormatter: TokenTextFormatter): String =
"${getTokenName(token)}[${token.line},${token.charPositionInLine}](${textFormatter.format(token.text)})"
fun shortFormatToken(token: Token): String =
"${getTokenName(token)}[${token.line},${token.charPositionInLine},${token.text.length}]"
getTokenName(token) + "[${formatTokenPositionShort(token)}](${textFormatter.format(token.text)})"
fun formatTokenText(text: String): String = excerptTokenParts(escapeTokenPartsToList(text))
fun formatTokenPosition(token: Token): String {
return "line ${token.line}, column ${token.charPositionInLine + 1}"
fun formatTokenPositionShort(token: Token): String {
val sourcePosition = getTokenSourcePosition(token)
return "${sourcePosition.line},${sourcePosition.column}"
}
fun getTokenSourcePosition(token: Token): SourcePosition {
return SourcePosition(token.line, token.charPositionInLine + 1)
}
fun excerptToken(token: Token) = excerptToken(token, 30, 7, "...")
@ -42,7 +45,7 @@ fun excerptToken(token: Token, startLength: Int = 30, endLength: Int = 7, separa
return excerptTokenParts(escapeTokenPartsToList(token.text), startLength, endLength, separator)
}
fun excerptTokenParts(
private fun excerptTokenParts(
parts: List<String>,
startLength: Int = 30,
endLength: Int = 7,

View File

@ -7,7 +7,7 @@ private operator fun Interval.component1(): Int = this.a
private operator fun Interval.component2(): Int = this.b
class WebViewComponentsTokenStream (private val tokenSource: TokenSource) : TokenStream {
class WebViewComponentsTokenStream(private val tokenSource: TokenSource) : TokenStream {
private val tokens: MutableList<Token> = ArrayList()

View File

@ -1,5 +1,6 @@
package groowt.view.component.web.ast;
import groowt.view.component.web.WebViewComponentBugError;
import groowt.view.component.web.antlr.MergedGroovyCodeToken;
import groowt.view.component.web.antlr.TokenUtil;
import groowt.view.component.web.antlr.WebViewComponentsParser;
@ -219,16 +220,18 @@ public class DefaultAstBuilderVisitor extends WebViewComponentsParserBaseVisitor
@Override
public Node visitComponentType(WebViewComponentsParser.ComponentTypeContext ctx) {
final var identifiers = ctx.Identifier();
if (identifiers.size() == 1) {
final TerminalNode first = identifiers.getFirst();
if (startsWithLowercaseLetter(first.getText())) {
return this.nodeFactory.stringComponentTypeNode(
this.getTokenRange(ctx), first.getSymbol().getTokenIndex()
);
}
final var typedIdentifier = ctx.TypedIdentifier();
if (typedIdentifier != null) {
return this.nodeFactory.classComponentTypeNode(this.getTokenRange(ctx));
}
return this.nodeFactory.classComponentTypeNode(this.getTokenRange(ctx));
final var stringIdentifier = ctx.StringIdentifier();
if (stringIdentifier != null) {
return this.nodeFactory.stringComponentTypeNode(
this.getTokenRange(ctx),
stringIdentifier.getSymbol().getTokenIndex()
);
}
throw new WebViewComponentBugError("Could not determine type of " + ctx);
}
@Override
@ -253,7 +256,7 @@ public class DefaultAstBuilderVisitor extends WebViewComponentsParserBaseVisitor
@Override
public Node visitKeyValueAttr(WebViewComponentsParser.KeyValueAttrContext ctx) {
final TerminalNode identifier = ctx.Identifier();
final TerminalNode identifier = ctx.AttributeIdentifier();
final KeyNode keyNode = this.nodeFactory.keyNode(TokenRange.of(
identifier.getSymbol(),
ctx.Equals().getSymbol()
@ -264,7 +267,7 @@ public class DefaultAstBuilderVisitor extends WebViewComponentsParserBaseVisitor
@Override
public Node visitBooleanAttr(WebViewComponentsParser.BooleanAttrContext ctx) {
final Token identifierToken = ctx.Identifier().getSymbol();
final Token identifierToken = ctx.AttributeIdentifier().getSymbol();
final KeyNode keyNode = this.nodeFactory.keyNode(
TokenRange.of(identifierToken),
identifierToken.getTokenIndex()

View File

@ -116,7 +116,7 @@ public final class CompilerPipeline {
// build ast
final var tokenList = new TokenList(parseResult.getTokenStream());
final var astBuilder = new DefaultAstBuilder(new DefaultNodeFactory(tokenList));
return (CompilationUnitNode) astBuilder.build(parseResult.getCompilationUnitContext());
return astBuilder.buildCompilationUnit(parseResult.getCompilationUnitContext());
}
private CompilerPipeline() {}

View File

@ -0,0 +1,12 @@
@file:JvmName("TextUtil")
package groowt.view.component.web.util
fun excerpt(s: String, startLength: Int = 30, endLength: Int = 7, ellipsis: String = "..."): String {
if (s.length > startLength + endLength + ellipsis.length) {
val start = s.substring(0..<startLength)
val end = s.substring((s.length - endLength)..<s.length)
return start + ellipsis + end
} else {
return s
}
}

View File

@ -1,75 +1,69 @@
CompilationUnitNode(1,1..21,6)
PreambleNode(1,1..6,1)
PreambleBreak[1,0](---\n)
GroovyCode[2,0](import some.Thing // a comment...World!')
PreambleBreak[4,30](\n---\n)
PreambleBreak[1,1](---\n)
GroovyCode[2,1](import some.Thing // a comment...World!')
PreambleBreak[4,31](\n---\n)
BodyNode(6,1..21,1)
JStringBodyTextNode(6,1..7,1)
RawText[6,0](<!DOCTYPE html>\n)
RawText[6,1](<!DOCTYPE html>\n)
TypedComponentNode(7,1..20,8)
ComponentArgsNode(7,2..7,6)
StringComponentTypeNode(7,2..7,6)
Identifier[7,1](html)
StringIdentifier[7,2](html)
BodyNode(7,7..20,1)
TypedComponentNode(8,5..8,18)
ComponentArgsNode(8,6..8,10)
StringComponentTypeNode(8,6..8,10)
Identifier[8,5](head)
StringIdentifier[8,6](head)
TypedComponentNode(9,5..19,12)
ComponentArgsNode(9,6..9,10)
StringComponentTypeNode(9,6..9,10)
Identifier[9,5](body)
StringIdentifier[9,6](body)
BodyNode(9,11..19,5)
TypedComponentNode(10,9..10,29)
ComponentArgsNode(10,10..10,12)
StringComponentTypeNode(10,10..10,12)
Identifier[10,9](h1)
StringIdentifier[10,10](h1)
BodyNode(10,13..10,24)
GStringBodyTextNode(10,13..10,24)
DollarScriptletNode(10,13..10,24)
DollarScriptletOpen[10,12](${)
GroovyCode[10,14](greeting)
DollarScriptletClose[10,22](})
DollarScriptletOpen[10,13](${)
GroovyCode[10,15](greeting)
DollarScriptletClose[10,23](})
TypedComponentNode(11,9..18,34)
ComponentArgsNode(11,10..11,32)
ClassComponentTypeNode(11,10..11,32)
Identifier[11,9](groowt)
Dot[11,15](.)
Identifier[11,16](view)
Dot[11,20](.)
Identifier[11,21](web)
Dot[11,24](.)
Identifier[11,25](Select)
TypedIdentifier[11,10](groowt.view.web.Select)
BodyNode(11,33..18,9)
TypedComponentNode(12,13..14,20)
ComponentArgsNode(12,14..12,36)
ClassComponentTypeNode(12,14..12,18)
Identifier[12,13](Case)
TypedIdentifier[12,14](Case)
KeyValueAttrNode(12,19..12,36)
KeyNode(12,19..12,24)
Identifier[12,18](cond)
Equals[12,22](=)
AttributeIdentifier[12,19](cond)
Equals[12,23](=)
ClosureValueNode(12,24..12,36)
ClosureAttrValueStart[12,23]({)
GroovyCode[12,24](isItTrue())
ClosureAttrValueEnd[12,34](})
ClosureAttrValueStart[12,24]({)
GroovyCode[12,25](isItTrue())
ClosureAttrValueEnd[12,35](})
BodyNode(12,37..14,13)
TypedComponentNode(13,17..13,37)
ComponentArgsNode(13,18..13,19)
StringComponentTypeNode(13,18..13,19)
Identifier[13,17](p)
StringIdentifier[13,18](p)
BodyNode(13,20..13,33)
JStringBodyTextNode(13,20..13,33)
RawText[13,19](It's true! :))
RawText[13,20](It's true! :))
TypedComponentNode(15,13..17,23)
ComponentArgsNode(15,14..15,21)
ClassComponentTypeNode(15,14..15,21)
Identifier[15,13](Default)
TypedIdentifier[15,14](Default)
BodyNode(15,22..17,13)
TypedComponentNode(16,17..16,40)
ComponentArgsNode(16,18..16,19)
StringComponentTypeNode(16,18..16,19)
Identifier[16,17](p)
StringIdentifier[16,18](p)
BodyNode(16,20..16,36)
JStringBodyTextNode(16,20..16,36)
RawText[16,19](It's false... :()
RawText[16,20](It's false... :()

View File

@ -2,9 +2,9 @@ CompilationUnitNode(1,1..2,6)
BodyNode(1,1..2,1)
GStringBodyTextNode(1,1..2,1)
JStringBodyTextNode(1,1..1,8)
RawText[1,0](Hello, )
RawText[1,1](Hello, )
DollarReferenceNode(1,8..1,15)
DollarReferenceStart[1,7]($)
GroovyCode[1,8](target)
DollarReferenceStart[1,8]($)
GroovyCode[1,9](target)
JStringBodyTextNode(1,15..2,1)
RawText[1,14](!\n)
RawText[1,15](!\n)

View File

@ -3,7 +3,7 @@ CompilationUnitNode(1,1..2,6)
TypedComponentNode(1,1..1,35)
ComponentArgsNode(1,2..1,10)
ClassComponentTypeNode(1,2..1,10)
Identifier[1,1](Greeting)
TypedIdentifier[1,2](Greeting)
BodyNode(1,11..1,24)
JStringBodyTextNode(1,11..1,24)
RawText[1,10](to the world!)
RawText[1,11](to the world!)

View File

@ -0,0 +1,20 @@
---
import some.Thing // a comment
def greeting = 'Hello, World!'
---
<!DOCTYPE html>
<html>
<head></head>
<body>
<h1>${greeting}</h1>
<groowt.view.web.Select>
<Case cond={isItTrue()}>
<p>It's true! :)</p>
</Case>
<Default>
<p>It's false... :(</p>
</Default>
</groowt.view.web.Select>
</body>
</html>

View File

@ -0,0 +1 @@
<data-test />

View File

@ -0,0 +1 @@
<html></html>

View File

@ -0,0 +1 @@
<groowt.Test></groowt.Test>

View File

@ -0,0 +1,83 @@
PreambleBreak[1,1](---\n)
GroovyCode[2,1](import some.Thing // a comment...World!')
PreambleBreak[4,31](\n---\n)
RawText[6,1](<!DOCTYPE html>\n)
ComponentOpen[7,1](<)
StringIdentifier[7,2](html)
ComponentClose[7,6](>)
RawText[7,7](\n )
ComponentOpen[8,5](<)
StringIdentifier[8,6](head)
ComponentClose[8,10](>)
ClosingComponentOpen[8,11](</)
StringIdentifier[8,13](head)
ComponentClose[8,17](>)
RawText[8,18](\n )
ComponentOpen[9,5](<)
StringIdentifier[9,6](body)
ComponentClose[9,10](>)
RawText[9,11](\n )
ComponentOpen[10,9](<)
StringIdentifier[10,10](h1)
ComponentClose[10,12](>)
DollarScriptletOpen[10,13](${)
GroovyCode[10,15](greeting)
DollarScriptletClose[10,23](})
ClosingComponentOpen[10,24](</)
StringIdentifier[10,26](h1)
ComponentClose[10,28](>)
RawText[10,29](\n )
ComponentOpen[11,9](<)
TypedIdentifier[11,10](groowt.view.web.Select)
ComponentClose[11,32](>)
RawText[11,33](\n )
ComponentOpen[12,13](<)
TypedIdentifier[12,14](Case)
ComponentNlws[12,18]( )
AttributeIdentifier[12,19](cond)
Equals[12,23](=)
ClosureAttrValueStart[12,24]({)
GroovyCode[12,25](isItTrue())
ClosureAttrValueEnd[12,35](})
ComponentClose[12,36](>)
RawText[12,37](\n )
ComponentOpen[13,17](<)
StringIdentifier[13,18](p)
ComponentClose[13,19](>)
RawText[13,20](It's true! :))
ClosingComponentOpen[13,33](</)
StringIdentifier[13,35](p)
ComponentClose[13,36](>)
RawText[13,37](\n )
ClosingComponentOpen[14,13](</)
TypedIdentifier[14,15](Case)
ComponentClose[14,19](>)
RawText[14,20](\n )
ComponentOpen[15,13](<)
TypedIdentifier[15,14](Default)
ComponentClose[15,21](>)
RawText[15,22](\n )
ComponentOpen[16,17](<)
StringIdentifier[16,18](p)
ComponentClose[16,19](>)
RawText[16,20](It's false... :()
ClosingComponentOpen[16,36](</)
StringIdentifier[16,38](p)
ComponentClose[16,39](>)
RawText[16,40](\n )
ClosingComponentOpen[17,13](</)
TypedIdentifier[17,15](Default)
ComponentClose[17,22](>)
RawText[17,23](\n )
ClosingComponentOpen[18,9](</)
TypedIdentifier[18,11](groowt.view.web.Select)
ComponentClose[18,33](>)
RawText[18,34](\n )
ClosingComponentOpen[19,5](</)
StringIdentifier[19,7](body)
ComponentClose[19,11](>)
RawText[19,12](\n)
ClosingComponentOpen[20,1](</)
StringIdentifier[20,3](html)
ComponentClose[20,7](>)
RawText[20,8](\n)

View File

@ -0,0 +1,5 @@
ComponentOpen[1,1](<)
StringIdentifier[1,2](data-test)
ComponentNlws[1,11]( )
ComponentSelfClose[1,12](/>)
RawText[1,14](\n)

View File

@ -0,0 +1,7 @@
ComponentOpen[1,1](<)
StringIdentifier[1,2](html)
ComponentClose[1,6](>)
ClosingComponentOpen[1,7](</)
StringIdentifier[1,9](html)
ComponentClose[1,13](>)
RawText[1,14](\n)

View File

@ -0,0 +1,7 @@
ComponentOpen[1,1](<)
TypedIdentifier[1,2](groowt.Test)
ComponentClose[1,13](>)
ClosingComponentOpen[1,14](</)
TypedIdentifier[1,16](groowt.Test)
ComponentClose[1,27](>)
RawText[1,28](\n)

View File

@ -1,4 +1,4 @@
RawText[1,0](Hello, )
DollarReferenceStart[1,7]($)
GroovyCode[1,8](target)
RawText[1,14](!\n)
RawText[1,1](Hello, )
DollarReferenceStart[1,8]($)
GroovyCode[1,9](target)
RawText[1,15](!\n)

View File

@ -1 +1 @@
RawText[1,0](Hello, World!\n)
RawText[1,1](Hello, World!\n)

View File

@ -0,0 +1 @@
<html></html>

View File

@ -13,7 +13,7 @@ compilationUnit[1,1..21,1]
ComponentOpen[7,1](<)
componentArgs[7,2..7,2]
componentType[7,2..7,2]
Identifier[7,2](html)
StringIdentifier[7,2](html)
ComponentClose[7,6](>)
body[7,7..19,12]
bodyText[7,7..7,7]
@ -25,12 +25,12 @@ compilationUnit[1,1..21,1]
ComponentOpen[8,5](<)
componentArgs[8,6..8,6]
componentType[8,6..8,6]
Identifier[8,6](head)
StringIdentifier[8,6](head)
ComponentClose[8,10](>)
closingComponent[8,11..8,17]
ClosingComponentOpen[8,11](</)
componentType[8,13..8,13]
Identifier[8,13](head)
StringIdentifier[8,13](head)
ComponentClose[8,17](>)
bodyText[8,18..8,18]
jStringBodyText[8,18..8,18]
@ -41,7 +41,7 @@ compilationUnit[1,1..21,1]
ComponentOpen[9,5](<)
componentArgs[9,6..9,6]
componentType[9,6..9,6]
Identifier[9,6](body)
StringIdentifier[9,6](body)
ComponentClose[9,10](>)
body[9,11..18,34]
bodyText[9,11..9,11]
@ -53,7 +53,7 @@ compilationUnit[1,1..21,1]
ComponentOpen[10,9](<)
componentArgs[10,10..10,10]
componentType[10,10..10,10]
Identifier[10,10](h1)
StringIdentifier[10,10](h1)
ComponentClose[10,12](>)
body[10,13..10,23]
bodyText[10,13..10,23]
@ -66,7 +66,7 @@ compilationUnit[1,1..21,1]
closingComponent[10,24..10,28]
ClosingComponentOpen[10,24](</)
componentType[10,26..10,26]
Identifier[10,26](h1)
StringIdentifier[10,26](h1)
ComponentClose[10,28](>)
bodyText[10,29..10,29]
jStringBodyText[10,29..10,29]
@ -75,15 +75,9 @@ compilationUnit[1,1..21,1]
componentWithChildren[11,9..18,33]
openComponent[11,9..11,32]
ComponentOpen[11,9](<)
componentArgs[11,10..11,26]
componentType[11,10..11,26]
Identifier[11,10](groowt)
Dot[11,16](.)
Identifier[11,17](view)
Dot[11,21](.)
Identifier[11,22](web)
Dot[11,25](.)
Identifier[11,26](Select)
componentArgs[11,10..11,10]
componentType[11,10..11,10]
TypedIdentifier[11,10](groowt.view.web.Select)
ComponentClose[11,32](>)
body[11,33..17,23]
bodyText[11,33..11,33]
@ -95,11 +89,11 @@ compilationUnit[1,1..21,1]
ComponentOpen[12,13](<)
componentArgs[12,14..12,35]
componentType[12,14..12,14]
Identifier[12,14](Case)
TypedIdentifier[12,14](Case)
ComponentNlws[12,18]( )
attr[12,19..12,35]
keyValueAttr[12,19..12,35]
Identifier[12,19](cond)
AttributeIdentifier[12,19](cond)
Equals[12,23](=)
value[12,24..12,35]
closureAttrValue[12,24..12,35]
@ -117,7 +111,7 @@ compilationUnit[1,1..21,1]
ComponentOpen[13,17](<)
componentArgs[13,18..13,18]
componentType[13,18..13,18]
Identifier[13,18](p)
StringIdentifier[13,18](p)
ComponentClose[13,19](>)
body[13,20..13,20]
bodyText[13,20..13,20]
@ -126,7 +120,7 @@ compilationUnit[1,1..21,1]
closingComponent[13,33..13,36]
ClosingComponentOpen[13,33](</)
componentType[13,35..13,35]
Identifier[13,35](p)
StringIdentifier[13,35](p)
ComponentClose[13,36](>)
bodyText[13,37..13,37]
jStringBodyText[13,37..13,37]
@ -134,7 +128,7 @@ compilationUnit[1,1..21,1]
closingComponent[14,13..14,19]
ClosingComponentOpen[14,13](</)
componentType[14,15..14,15]
Identifier[14,15](Case)
TypedIdentifier[14,15](Case)
ComponentClose[14,19](>)
bodyText[14,20..14,20]
jStringBodyText[14,20..14,20]
@ -145,7 +139,7 @@ compilationUnit[1,1..21,1]
ComponentOpen[15,13](<)
componentArgs[15,14..15,14]
componentType[15,14..15,14]
Identifier[15,14](Default)
TypedIdentifier[15,14](Default)
ComponentClose[15,21](>)
body[15,22..16,40]
bodyText[15,22..15,22]
@ -157,7 +151,7 @@ compilationUnit[1,1..21,1]
ComponentOpen[16,17](<)
componentArgs[16,18..16,18]
componentType[16,18..16,18]
Identifier[16,18](p)
StringIdentifier[16,18](p)
ComponentClose[16,19](>)
body[16,20..16,20]
bodyText[16,20..16,20]
@ -166,7 +160,7 @@ compilationUnit[1,1..21,1]
closingComponent[16,36..16,39]
ClosingComponentOpen[16,36](</)
componentType[16,38..16,38]
Identifier[16,38](p)
StringIdentifier[16,38](p)
ComponentClose[16,39](>)
bodyText[16,40..16,40]
jStringBodyText[16,40..16,40]
@ -174,21 +168,15 @@ compilationUnit[1,1..21,1]
closingComponent[17,13..17,22]
ClosingComponentOpen[17,13](</)
componentType[17,15..17,15]
Identifier[17,15](Default)
TypedIdentifier[17,15](Default)
ComponentClose[17,22](>)
bodyText[17,23..17,23]
jStringBodyText[17,23..17,23]
RawText[17,23](\n )
closingComponent[18,9..18,33]
ClosingComponentOpen[18,9](</)
componentType[18,11..18,27]
Identifier[18,11](groowt)
Dot[18,17](.)
Identifier[18,18](view)
Dot[18,22](.)
Identifier[18,23](web)
Dot[18,26](.)
Identifier[18,27](Select)
componentType[18,11..18,11]
TypedIdentifier[18,11](groowt.view.web.Select)
ComponentClose[18,33](>)
bodyText[18,34..18,34]
jStringBodyText[18,34..18,34]
@ -196,7 +184,7 @@ compilationUnit[1,1..21,1]
closingComponent[19,5..19,11]
ClosingComponentOpen[19,5](</)
componentType[19,7..19,7]
Identifier[19,7](body)
StringIdentifier[19,7](body)
ComponentClose[19,11](>)
bodyText[19,12..19,12]
jStringBodyText[19,12..19,12]
@ -204,7 +192,7 @@ compilationUnit[1,1..21,1]
closingComponent[20,1..20,7]
ClosingComponentOpen[20,1](</)
componentType[20,3..20,3]
Identifier[20,3](html)
StringIdentifier[20,3](html)
ComponentClose[20,7](>)
bodyText[20,8..20,8]
jStringBodyText[20,8..20,8]

View File

@ -0,0 +1,19 @@
compilationUnit[1,1..2,1]
body[1,1..1,14]
component[1,1..1,13]
componentWithChildren[1,1..1,13]
openComponent[1,1..1,6]
ComponentOpen[1,1](<)
componentArgs[1,2..1,2]
componentType[1,2..1,2]
StringIdentifier[1,2](html)
ComponentClose[1,6](>)
closingComponent[1,7..1,13]
ClosingComponentOpen[1,7](</)
componentType[1,9..1,9]
StringIdentifier[1,9](html)
ComponentClose[1,13](>)
bodyText[1,14..1,14]
jStringBodyText[1,14..1,14]
RawText[1,14](\n)
EOF[2,1](<EOF>)

View File

@ -5,7 +5,6 @@ import groowt.view.component.web.antlr.TokenList;
import groowt.view.component.web.ast.DefaultAstBuilder;
import groowt.view.component.web.ast.DefaultNodeFactory;
import groowt.view.component.web.ast.node.BodyNode;
import groowt.view.component.web.ast.node.CompilationUnitNode;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit;
import groowt.view.component.web.transpile.BodyTranspiler;
import groowt.view.component.web.transpile.TranspilerConfiguration;
@ -39,7 +38,7 @@ public abstract class BodyTranspilerTests {
final var parseResult = ParserUtil.parseCompilationUnit(source);
final var tokenList = new TokenList(parseResult.getTokenStream());
final var b = new DefaultAstBuilder(new DefaultNodeFactory(tokenList));
final var cuNode = (CompilationUnitNode) b.build(parseResult.getCompilationUnitContext());
final var cuNode = b.buildCompilationUnit(parseResult.getCompilationUnitContext());
final var bodyNode = cuNode.getBodyNode();
if (bodyNode == null) {
fail("No BodyNode was built for source: " + source);

View File

@ -5,7 +5,6 @@ import groowt.view.component.web.antlr.TokenList;
import groowt.view.component.web.ast.DefaultAstBuilder;
import groowt.view.component.web.ast.DefaultNodeFactory;
import groowt.view.component.web.ast.node.BodyNode;
import groowt.view.component.web.ast.node.CompilationUnitNode;
import groowt.view.component.web.ast.node.GStringBodyTextNode;
import groowt.view.component.web.transpile.GStringTranspiler;
import org.codehaus.groovy.ast.expr.ClosureExpression;
@ -34,7 +33,7 @@ public abstract class GStringTranspilerTests {
final var tokenList = new TokenList(parseResult.getTokenStream());
final var nodeFactory = new DefaultNodeFactory(tokenList);
final var astBuilder = new DefaultAstBuilder(nodeFactory);
final var cuNode = (CompilationUnitNode) astBuilder.build(parseResult.getCompilationUnitContext());
final var cuNode = astBuilder.buildCompilationUnit(parseResult.getCompilationUnitContext());
return Objects.requireNonNull(cuNode.getBodyNode());
}

View File

@ -9,7 +9,6 @@ import groowt.view.component.web.antlr.ParserUtil;
import groowt.view.component.web.antlr.TokenList;
import groowt.view.component.web.ast.DefaultAstBuilder;
import groowt.view.component.web.ast.DefaultNodeFactory;
import groowt.view.component.web.ast.node.CompilationUnitNode;
import groowt.view.component.web.compiler.AnonymousWebViewComponent;
import groowt.view.component.web.compiler.DefaultWebViewComponentTemplateCompileUnit;
import groowt.view.component.web.transpile.GroovyTranspiler;
@ -39,7 +38,7 @@ public abstract class GroovyTranspilerTests {
final var parseResult = ParserUtil.parseCompilationUnit(source);
final var tokenList = new TokenList(parseResult.getTokenStream());
final var astBuilder = new DefaultAstBuilder(new DefaultNodeFactory(tokenList));
final var cuNode = (CompilationUnitNode) astBuilder.build(parseResult.getCompilationUnitContext());
final var cuNode = astBuilder.buildCompilationUnit(parseResult.getCompilationUnitContext());
try {
this.transpiler.transpile(
new DefaultComponentTemplateCompilerConfiguration(),

View File

@ -3,8 +3,10 @@ package groowt.view.component.web.tools
import groovy.transform.InheritConstructors
import groowt.view.component.web.antlr.*
import groowt.view.component.web.antlr.AntlrUtil.ParseErrorCollector
import groowt.view.component.web.antlr.WebViewComponentsParser.CompilationUnitContext
import groowt.view.component.web.util.ExtensionUtil
import org.antlr.v4.runtime.CharStreams
import org.antlr.v4.runtime.ConsoleErrorListener
@InheritConstructors
final class ParseTreeFileMaker extends AbstractOutputFileMaker {
@ -36,7 +38,7 @@ final class ParseTreeFileMaker extends AbstractOutputFileMaker {
private boolean onErrors(
String name,
WebViewComponentsParser parser,
WebViewComponentsParser.CompilationUnitContext cu,
CompilationUnitContext cu,
ParseErrorCollector errors
) {
def errorCount = errors.errorCount
@ -53,14 +55,35 @@ final class ParseTreeFileMaker extends AbstractOutputFileMaker {
}
}
private Tuple3<WebViewComponentsParser, WebViewComponentsParser.CompilationUnitContext, ParseErrorCollector> parse(
File sourceFile
) {
private Tuple3<WebViewComponentsParser, CompilationUnitContext, ParseErrorCollector> parse(File sourceFile) {
def input = CharStreams.fromFileName(sourceFile.toString())
def lexer = new WebViewComponentsLexer(input)
def lexerErrorListener = new LexerErrorListener()
lexer.removeErrorListener(ConsoleErrorListener.INSTANCE)
lexer.addErrorListener(lexerErrorListener)
def tokenStream = new WebViewComponentsTokenStream(lexer)
def parser = new WebViewComponentsParser(tokenStream)
def parserErrorListener = new ParserErrorListener()
parser.removeErrorListener(ConsoleErrorListener.INSTANCE)
parser.addErrorListener(parserErrorListener)
def cu = parser.compilationUnit()
if (!lexerErrorListener.errors.isEmpty()) {
println 'There were lexer errors.'
lexerErrorListener.errors.each { println LexerErrorKt.format(it) }
return null
}
if (!parserErrorListener.errors.isEmpty()) {
println 'There were parser errors.'
parserErrorListener.errors.each { println ParserErrorKt.format(it) }
return null
}
def errors = AntlrUtil.findErrorNodes(cu)
new Tuple3<>(parser, cu, errors)
}
@ -71,11 +94,27 @@ final class ParseTreeFileMaker extends AbstractOutputFileMaker {
println "Processing: $name"
boolean doneYet = false
while (!doneYet) {
def (parser, cu, errors) = this.parse(sourceFile)
if (errors.isEmpty()) {
doneYet = this.onSuccess(name, parser, cu)
} else {
doneYet = this.onErrors(name, parser, cu, errors)
final WebViewComponentsParser parser
final CompilationUnitContext cu
final ParseErrorCollector errors
try {
def result = this.parse(sourceFile)
if (result != null) {
(parser, cu, errors) = result
if (errors.isEmpty()) {
doneYet = this.onSuccess(name, parser, cu)
} else {
doneYet = this.onErrors(name, parser, cu, errors)
}
} else {
doneYet = !this.getYesNoInput('Would you like to try again? (y/n)', true)
}
} catch (Exception e) {
println "There was an exception: $e"
if (this.verbose) {
e.printStackTrace()
}
doneYet = !this.getYesNoInput('Would you like to try again? (y/n)', true)
}
}
}

View File

@ -1,11 +1,10 @@
package groowt.view.component.web.tools
import groovy.transform.InheritConstructors
import groowt.view.component.web.antlr.TokenUtil
import groowt.view.component.web.antlr.WebViewComponentsLexer
import groowt.view.component.web.antlr.WebViewComponentsTokenStream
import groowt.view.component.web.antlr.*
import groowt.view.component.web.util.ExtensionUtil
import org.antlr.v4.runtime.CharStreams
import org.antlr.v4.runtime.ConsoleErrorListener
import org.antlr.v4.runtime.Token
@InheritConstructors
@ -25,6 +24,18 @@ class TokensFileMaker extends AbstractOutputFileMaker {
}
}
protected boolean onLexerErrors(String name, List<LexerError> errors) {
println "There were lexer errors in $name."
errors.each { println LexerError.format(it) }
if (this.getYesNoInput('Do you wish to try again? (y/n)', true)) {
println "Trying $name again..."
return false
} else {
println "Skipping $name..."
return true
}
}
protected boolean onException(String name, Exception e) {
println "There was an exception while tokenizing $name: $e.message"
e.printStackTrace()
@ -46,8 +57,16 @@ class TokensFileMaker extends AbstractOutputFileMaker {
try {
def input = CharStreams.fromString(sourceFile.getText())
def lexer = new WebViewComponentsLexer(input)
lexer.removeErrorListener(ConsoleErrorListener.INSTANCE)
def lexerErrorListener = new LexerErrorListener()
lexer.addErrorListener(lexerErrorListener)
def tokenStream = new WebViewComponentsTokenStream(lexer)
doneYet = this.onSuccess(name, tokenStream.getAllTokensSkipEOF())
def allTokens = tokenStream.getAllTokensSkipEOF()
if (!lexerErrorListener.errors.isEmpty()) {
} else {
doneYet = this.onSuccess(name, allTokens)
}
} catch (Exception e) {
doneYet = this.onException(name, e)
}

View File

@ -2,11 +2,11 @@
package groowt.view.component.web.tools
import groowt.view.component.web.antlr.formatToken
import groowt.view.component.web.antlr.runLexerAllTokens
import groowt.view.component.web.antlr.runLexerAllTokensRaw
fun main(args: Array<String>) {
val options = processArgs(args)
configureLog(options.logLevel)
val input = getInput(options.source)
runLexerAllTokens(input).forEachIndexed { i, t -> println("$i: ${formatToken(t)}") }
runLexerAllTokensRaw(input).forEachIndexed { i, t -> println("$i: ${formatToken(t)}") }
}