diff --git a/web-view-components-compiler/build.gradle b/web-view-components-compiler/build.gradle index ee538f5..83cb94c 100644 --- a/web-view-components-compiler/build.gradle +++ b/web-view-components-compiler/build.gradle @@ -10,6 +10,7 @@ plugins { id 'groovy' id 'org.jetbrains.kotlin.jvm' id 'java-test-fixtures' + id 'distribution' } repositories { @@ -104,6 +105,12 @@ tasks.named('generateWebViewComponentsLexerBase', GroowtAntlrExecTask) { task -> } } +tasks.named({ String name -> + name in ['compileJava', 'compileGroovy', 'compileKotlin'] +} as Spec).configureEach { + dependsOn 'generateAllAntlr' +} + tasks.register('groovyConsole', JavaExec) { group = 'groovy' classpath += sourceSets.main.runtimeClasspath + configurations.groovyConsole @@ -112,14 +119,13 @@ tasks.register('groovyConsole', JavaExec) { tasks.register('toolsJar', Jar) { group 'tools' - archiveBaseName = 'web-tools' + archiveBaseName = 'web-view-components-compiler-tools' exclude { FileTreeElement element -> element.file in sourceSets.main.output } from sourceSets.tools.output from sourceSets.tools.runtimeClasspath.filter(File.&exists).collect { it.isDirectory() ? it : zipTree(it) } duplicatesStrategy = DuplicatesStrategy.EXCLUDE - dependsOn tasks.named('jar') } @NullCheck @@ -161,9 +167,9 @@ toolSpecs.each { spec -> group = 'tools' outputDir = file('bin') applicationName = spec.name + classpath = sourceSets.tools.runtimeClasspath mainClass = spec.fullMainClass unixStartScriptGenerator.template = resources.text.fromFile('src/tools/binTemplate.gst') - dependsOn tasks.named('toolsJar') } } @@ -198,6 +204,12 @@ java { withSourcesJar() } +tasks.named('sourcesJar', Jar) { + from fileTree('src/main/antlr') + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + dependsOn 'generateAllAntlr' +} + publishing { publications { create('webViewComponentsCompiler', MavenPublication) { @@ -206,4 +218,3 @@ publishing { } } } - diff --git a/web-view-components-compiler/src/main/antlr/WebViewComponentsLexerBase.g4 b/web-view-components-compiler/src/main/antlr/WebViewComponentsLexerBase.g4 index a9f56dc..aa2df0e 100644 --- a/web-view-components-compiler/src/main/antlr/WebViewComponentsLexerBase.g4 +++ b/web-view-components-compiler/src/main/antlr/WebViewComponentsLexerBase.g4 @@ -8,6 +8,7 @@ options { tokens { PreambleBreak, + ComponentNlws, GroovyCode, GStringAttrValueEnd, JStringAttrValueEnd, @@ -15,6 +16,10 @@ tokens { DollarScriptletClose } +channels { + ERROR +} + @header { import java.util.Set; import static groowt.view.component.web.antlr.LexerSemanticPredicates.*; @@ -83,6 +88,10 @@ tokens { DollarSlashyStringClosureStart ); + public WebViewComponentsLexerBase() { + _interp = new LexerATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache); + } + private void onPreambleClose() { this.setType(PreambleBreak); this.exitPreamble(); @@ -112,11 +121,19 @@ PreambleOpen ; ComponentOpen - : LT -> pushMode(TAG_START) + : LT { !isAnyOf(this.getNextChar(), '/', '>') }? -> pushMode(TAG_START) ; ClosingComponentOpen - : LT FS -> pushMode(TAG_START) + : LT FS { !this.isNext('>') }? -> pushMode(TAG_START) + ; + +FragmentOpen + : LT GT + ; + +FragmentClose + : LT FS GT ; EqualsScriptletOpen @@ -161,10 +178,6 @@ RawText // ---------------------------------------- mode TAG_START; -FragmentClose - : GT -> popMode - ; - TypedIdentifier : ( PackageIdentifier DOT )* ClassIdentifier ( DOT ClassIdentifier )* -> mode(IN_TAG) ; @@ -213,6 +226,14 @@ StringIdentifierChar : [-_0-9\p{L}] ; +TagStartNlws + : NLWS+ -> type(ComponentNlws), channel(HIDDEN) + ; + +TagStartError + : . -> channel(ERROR) + ; + // ---------------------------------------- mode IN_TAG; @@ -255,7 +276,7 @@ JStringAttrValueStart ; ClosureAttrValueStart - : LEFT_CURLY ComponentNlws? { !this.isNext('<') }? + : LEFT_CURLY InTagNlws? { !this.isNext('<') }? { this.curlies.push(() -> { this.setType(ClosureAttrValueEnd); @@ -267,15 +288,19 @@ ClosureAttrValueStart ; ComponentAttrValueStart - : LEFT_CURLY ComponentNlws? { this.isNext('<') }? + : LEFT_CURLY InTagNlws? { this.isNext('<') }? ; ComponentAttrValueEnd : GT RIGHT_CURLY ; -ComponentNlws - : NLWS+ +InTagNlws + : NLWS+ -> type(ComponentNlws), channel(HIDDEN) + ; + +TagError + : . -> channel(ERROR) ; // ---------------------------------------- diff --git a/web-view-components-compiler/src/main/antlr/WebViewComponentsParser.g4 b/web-view-components-compiler/src/main/antlr/WebViewComponentsParser.g4 index 32fe70c..e274743 100644 --- a/web-view-components-compiler/src/main/antlr/WebViewComponentsParser.g4 +++ b/web-view-components-compiler/src/main/antlr/WebViewComponentsParser.g4 @@ -49,21 +49,20 @@ openComponent ; closingComponent - : ClosingComponentOpen ComponentNlws? - componentType ComponentNlws? + : ClosingComponentOpen + componentType ComponentClose ; fragmentComponent - : ComponentOpen ComponentClose - body - ClosingComponentOpen ComponentClose + : FragmentOpen body FragmentClose ; componentArgs - : ComponentNlws? componentType - ComponentNlws? componentConstructor? - ComponentNlws? ( attr ComponentNlws? )* + : componentType + ( attr )* + componentConstructor? + ( attr )* ; componentType diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/AbstractWebViewComponentsLexer.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/AbstractWebViewComponentsLexer.java index 15657ec..40875e0 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/AbstractWebViewComponentsLexer.java +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/AbstractWebViewComponentsLexer.java @@ -45,8 +45,8 @@ public abstract class AbstractWebViewComponentsLexer extends Lexer { protected record StringClosingEndSpec(int type, int popCount) implements GStringPathEndSpec {} - protected final PairCounter curlies = new SimplePairCounter(this); - protected final PairCounter parentheses = new SimplePairCounter(this); + protected PairCounter curlies = new SimplePairCounter(); + protected PairCounter parentheses = new SimplePairCounter(); private final Logger logger; @@ -57,6 +57,58 @@ public abstract class AbstractWebViewComponentsLexer extends Lexer { public AbstractWebViewComponentsLexer(CharStream input) { super(input); this.logger = LoggerFactory.getLogger(this.getClass()); + this.curlies.setLexer(this); + this.parentheses.setLexer(this); + } + + public AbstractWebViewComponentsLexer() { + this.logger = LoggerFactory.getLogger(this.getClass()); + this.curlies.setLexer(this); + this.parentheses.setLexer(this); + } + + public PairCounter getCurlies() { + return this.curlies; + } + + public void setCurlies(PairCounter curlies) { + this.curlies = curlies; + } + + public PairCounter getParentheses() { + return this.parentheses; + } + + public void setParentheses(PairCounter parentheses) { + this.parentheses = parentheses; + } + + public Logger getLogger() { + return this.logger; + } + + public boolean isCanPreamble() { + return this.canPreamble; + } + + public void setCanPreamble(boolean canPreamble) { + this.canPreamble = canPreamble; + } + + public boolean isInPreamble() { + return this.inPreamble; + } + + public void setInPreamble(boolean inPreamble) { + this.inPreamble = inPreamble; + } + + public boolean isInConstructor() { + return this.inConstructor; + } + + public void setInConstructor(boolean inConstructor) { + this.inConstructor = inConstructor; } @Override diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/LexerError.kt b/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/LexerError.kt index f6e9552..a3467aa 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/LexerError.kt +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/LexerError.kt @@ -2,8 +2,15 @@ package groowt.view.component.web.antlr import groowt.view.component.web.util.SourcePosition -data class LexerError(val type: LexerErrorType, val sourcePosition: SourcePosition) +data class LexerError( + val type: LexerErrorType, + val sourcePosition: SourcePosition, + val badText: String, + val lexerMode: Int +) -fun format(lexerError: LexerError): String { - return "At ${lexerError.sourcePosition.toStringLong()}: ${lexerError.type.message}" +fun formatLexerError(lexerError: LexerError): String { + return "At ${lexerError.sourcePosition.toStringLong()}: ${lexerError.type.message} " + + "Bad input: '${escapeChars(lexerError.badText)}'. " + + "(mode: ${WebViewComponentsLexer.modeNames[lexerError.lexerMode]})." } diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/LexerErrorListener.kt b/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/LexerErrorListener.kt index dfe2f11..9fcfb53 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/LexerErrorListener.kt +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/LexerErrorListener.kt @@ -4,6 +4,7 @@ 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 org.antlr.v4.runtime.misc.Interval import java.util.* class LexerErrorListener : ANTLRErrorListener { @@ -22,7 +23,12 @@ class LexerErrorListener : ANTLRErrorListener { ) { if (e is LexerNoViableAltException) { val sourcePosition = SourcePosition(line, charPositionInLine + 1) - val lexerError = LexerError(LexerErrorType.NO_VIABLE_ALTERNATIVE, sourcePosition) + val lexerError = LexerError( + LexerErrorType.NO_VIABLE_ALTERNATIVE, + sourcePosition, + e.inputStream.getText(Interval.of(e.startIndex, e.startIndex)), + (recognizer as WebViewComponentsLexer)._mode + ) errors.add(lexerError) } else { throw e diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/LexerErrorType.kt b/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/LexerErrorType.kt index 85a3683..8f79361 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/LexerErrorType.kt +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/LexerErrorType.kt @@ -1,5 +1,5 @@ package groowt.view.component.web.antlr enum class LexerErrorType(val message: String) { - NO_VIABLE_ALTERNATIVE("No viable alternative.") + NO_VIABLE_ALTERNATIVE("Lexer has no viable alternative.") } diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/PairCounter.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/PairCounter.java index 5b2ac2c..15c5544 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/PairCounter.java +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/PairCounter.java @@ -1,5 +1,11 @@ package groowt.view.component.web.antlr; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.misc.IntegerStack; +import org.jetbrains.annotations.Nullable; + +import java.util.Deque; + public interface PairCounter { @FunctionalInterface @@ -7,6 +13,17 @@ public interface PairCounter { void after(); } + interface StackEntry { + IntegerStack getModes(); + @Nullable OnPop getOnPop(); + int get(); + int increment(); + int decrement(); + } + + Deque getStack(); + void setLexer(Lexer lexer); + void push(); void push(OnPop onPop); void pop(); diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/ParserError.kt b/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/ParserError.kt index 2d95cf0..f2d78da 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/ParserError.kt +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/ParserError.kt @@ -14,7 +14,7 @@ class MismatchedInputParserError( val expectedTokenTypes: Set ) : ParserError(type, offending, context) -fun format(error: ParserError): String { +fun formatParserError(error: ParserError): String { val sb = StringBuilder() val sourcePosition = SourcePosition.fromStartOfToken(error.offending) sb.append("At ") diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/ParserErrorListener.kt b/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/ParserErrorListener.kt index 1c8e024..a3c098b 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/ParserErrorListener.kt +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/ParserErrorListener.kt @@ -1,12 +1,16 @@ package groowt.view.component.web.antlr +import groowt.view.component.web.util.SourcePosition import org.antlr.v4.runtime.* +import org.antlr.v4.runtime.misc.Interval class ParserErrorListener : BaseErrorListener() { - private val errors: MutableList = ArrayList() + private val lexerErrors: MutableList = ArrayList() + private val parserErrors: MutableList = ArrayList() - fun getErrors(): List = this.errors + fun getLexerErrors(): List = this.lexerErrors + fun getParserErrors(): List = this.parserErrors override fun syntaxError( recognizer: Recognizer<*, *>, @@ -18,9 +22,16 @@ class ParserErrorListener : BaseErrorListener() { ) { val parser = recognizer as WebViewComponentsParser when (e) { + is LexerNoViableAltException -> { + val lexer = e.recognizer as WebViewComponentsLexer + val sourcePosition = SourcePosition(line, charPositionInLine + 1) + val badText = lexer.inputStream.getText(Interval.of(e.startIndex, e.startIndex)) + val error = LexerError(LexerErrorType.NO_VIABLE_ALTERNATIVE, sourcePosition, badText, lexer._mode) + this.lexerErrors.add(error) + } is NoViableAltException -> { val error = ParserError(ParserErrorType.NO_VIABLE_ALTERNATIVE, e.offendingToken, parser.context) - errors.add(error) + parserErrors.add(error) } is InputMismatchException -> { val error = MismatchedInputParserError( @@ -29,11 +40,11 @@ class ParserErrorListener : BaseErrorListener() { parser.context, e.expectedTokens.toSet() ) - errors.add(error) + parserErrors.add(error) } is FailedPredicateException -> { val error = ParserError(ParserErrorType.FAILED_PREDICATE, e.offendingToken, parser.context) - errors.add(error) + parserErrors.add(error) } } } diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/ParserErrorType.kt b/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/ParserErrorType.kt index 8470b69..00a18b3 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/ParserErrorType.kt +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/ParserErrorType.kt @@ -1,7 +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.") + NO_VIABLE_ALTERNATIVE("Parser has no viable alternative."), + INPUT_MISMATCH("Parser input mismatch."), + FAILED_PREDICATE("Parser input failed predicate.") } diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/ParserUtil.kt b/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/ParserUtil.kt index 36cb4e5..210860a 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/ParserUtil.kt +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/ParserUtil.kt @@ -18,6 +18,8 @@ data class CompilationUnitParseResult( val lexer: WebViewComponentsLexer, val tokenStream: WebViewComponentsTokenStream, val parser: WebViewComponentsParser, + val lexerErrors: List, + val parserErrors: List, val compilationUnitContext: CompilationUnitContext ) @@ -33,9 +35,21 @@ fun parseCompilationUnit(reader: Reader): CompilationUnitParseResult = fun parseCompilationUnit(charStream: CharStream): CompilationUnitParseResult { val lexer = WebViewComponentsLexer(charStream) val tokenStream = WebViewComponentsTokenStream(lexer) + val parser = WebViewComponentsParser(tokenStream) + parser.removeErrorListener(ConsoleErrorListener.INSTANCE) + val parserErrorListener = ParserErrorListener() + parser.addErrorListener(parserErrorListener) + val cu = parser.compilationUnit() - return CompilationUnitParseResult(lexer, tokenStream, parser, cu) + return CompilationUnitParseResult( + lexer, + tokenStream, + parser, + parserErrorListener.getLexerErrors(), + parserErrorListener.getParserErrors(), + cu + ) } fun parseCompilationUnit( diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/SimplePairCounter.kt b/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/SimplePairCounter.kt index 226527a..e9d079d 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/SimplePairCounter.kt +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/SimplePairCounter.kt @@ -1,48 +1,76 @@ package groowt.view.component.web.antlr +import groowt.view.component.web.antlr.PairCounter.StackEntry import org.antlr.v4.runtime.Lexer import org.antlr.v4.runtime.misc.IntegerStack import org.slf4j.Logger import org.slf4j.LoggerFactory import java.util.* -class SimplePairCounter(private val lexer: Lexer) : PairCounter { +class SimplePairCounter : PairCounter { companion object { private val logger: Logger = LoggerFactory.getLogger(SimplePairCounter::class.java) } - private class StackEntry(val modes: IntegerStack, val onPop: PairCounter.OnPop?) { + class SimpleStackEntry(private val modes: IntegerStack, private val onPop: PairCounter.OnPop?) : StackEntry { + private var count: Int = 0 - fun get() = this.count - fun increment(): Int = ++this.count - fun decrement(): Int = --this.count + + override fun getModes() = modes + override fun getOnPop() = onPop + override fun get() = this.count + override fun increment(): Int = ++this.count + override fun decrement(): Int = --this.count + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is StackEntry) return false + return modes == other.modes + && onPop == other.onPop + && get() == other.get() + } + + override fun hashCode() = Objects.hash(modes, onPop, count) + + override fun toString() = "SimpleStackEntry($count)" + } - private val stack: Deque = LinkedList() + private val stack: Deque = LinkedList() + private var lexer: Lexer? = null - private fun currentEntry(): StackEntry { + override fun getStack(): Deque = LinkedList(stack) + + override fun setLexer(lexer: Lexer?) { + this.lexer = lexer + } + + private fun currentEntry(): SimpleStackEntry { if (this.stack.isEmpty()) { throw IllegalStateException("Cannot currentEntry() when stack is empty!") } return this.stack.peek() } - private fun getLexerModes() = IntegerStack(this.lexer._modeStack).also { it.push(this.lexer._mode) } + private fun getLexerModes(): IntegerStack { + if (lexer == null) { + throw IllegalStateException("lexer is null") + } + return IntegerStack(lexer!!._modeStack).also { it.push(lexer!!._mode) } + } override fun push() { - this.stack.push(StackEntry(this.getLexerModes(), null)) + this.stack.push(SimpleStackEntry(this.getLexerModes(), null)) } override fun push(onPop: PairCounter.OnPop?) { - this.stack.push(StackEntry(this.getLexerModes(), onPop)) + this.stack.push(SimpleStackEntry(this.getLexerModes(), onPop)) } override fun pop() { val entry = this.stack.pop() - if (entry.onPop != null) { - entry.onPop.after() - } + entry.onPop?.after() if (logger.isWarnEnabled) { val currentModes = this.getLexerModes() if (entry.modes != currentModes) { @@ -80,4 +108,12 @@ class SimplePairCounter(private val lexer: Lexer) : PairCounter { this.stack.clear() } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is PairCounter) return false + return stack == other.stack + } + + override fun hashCode(): Int = Objects.hash(stack) + } diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/WebViewComponentsLexer.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/WebViewComponentsLexer.java index c392394..cb64135 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/WebViewComponentsLexer.java +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/WebViewComponentsLexer.java @@ -9,6 +9,10 @@ public class WebViewComponentsLexer extends WebViewComponentsLexerBase { this._interp = new PositionAdjustingLexerATNSimulator(this, _ATN, _decisionToDFA, _sharedContextCache); } + public WebViewComponentsLexer() { + super(); + } + @Override protected PositionAdjustingLexerATNSimulator getPositionAdjustingInterpreter() { return (PositionAdjustingLexerATNSimulator) this.getInterpreter(); diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/WebViewComponentsTokenStream.kt b/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/WebViewComponentsTokenStream.kt index be3bb2e..7d8e63a 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/WebViewComponentsTokenStream.kt +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/WebViewComponentsTokenStream.kt @@ -7,7 +7,15 @@ 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, + private val ignoreChannels: Set +) : TokenStream { + + constructor(lexer: WebViewComponentsLexer) : this( + lexer, + setOf(WebViewComponentsLexer.HIDDEN, WebViewComponentsLexer.ERROR) + ) private val tokens: MutableList = ArrayList() @@ -67,7 +75,7 @@ class WebViewComponentsTokenStream(private val tokenSource: TokenSource) : Token MergedGroovyCodeToken(groovyTokens, this.tokens.size, this.tokenSource, this.tokenSource.inputStream) ) } - if (followingToken != null) { + if (followingToken != null && followingToken.channel !in this.ignoreChannels) { fetched++ if (followingToken is WritableToken) { followingToken.tokenIndex = this.tokens.size @@ -191,8 +199,7 @@ class WebViewComponentsTokenStream(private val tokenSource: TokenSource) : Token } fun getAllTokensSkipEOF(): List { - this.fill() - return this.tokens.filter { it.type != Token.EOF } + return this.getAllTokens().filter { it.type != Token.EOF } } private fun fill() { diff --git a/web-view-components-compiler/src/test/java/groowt/view/component/web/antlr/WebViewComponentsLexerTests.java b/web-view-components-compiler/src/test/java/groowt/view/component/web/antlr/WebViewComponentsLexerTests.java index 94a1db2..6faf111 100644 --- a/web-view-components-compiler/src/test/java/groowt/view/component/web/antlr/WebViewComponentsLexerTests.java +++ b/web-view-components-compiler/src/test/java/groowt/view/component/web/antlr/WebViewComponentsLexerTests.java @@ -13,6 +13,7 @@ import org.junit.jupiter.api.TestFactory; import java.nio.file.Path; import java.util.Collection; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import static groowt.view.component.web.antlr.WebViewComponentsLexer.*; @@ -70,7 +71,10 @@ public class WebViewComponentsLexerTests { sourceFile -> { final CharStream input = CharStreams.fromString(FileUtil.readFile(sourceFile)); final WebViewComponentsLexer lexer = new WebViewComponentsLexer(input); - final WebViewComponentsTokenStream tokenStream = new WebViewComponentsTokenStream(lexer); + final WebViewComponentsTokenStream tokenStream = new WebViewComponentsTokenStream( + lexer, + Set.of(WebViewComponentsLexer.HIDDEN) // include bad tokens for testing + ); final List allTokens = tokenStream.getAllTokensSkipEOF(); final var sb = new StringBuilder(); for (int i = 0; i < allTokens.size(); i++) { diff --git a/web-view-components-compiler/src/test/java/groowt/view/component/web/antlr/WebViewComponentsParserTests.java b/web-view-components-compiler/src/test/java/groowt/view/component/web/antlr/WebViewComponentsParserTests.java index 723627d..4edbfb6 100644 --- a/web-view-components-compiler/src/test/java/groowt/view/component/web/antlr/WebViewComponentsParserTests.java +++ b/web-view-components-compiler/src/test/java/groowt/view/component/web/antlr/WebViewComponentsParserTests.java @@ -5,6 +5,8 @@ import org.antlr.v4.runtime.CharStreams; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.function.Executable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileInputStream; @@ -22,6 +24,8 @@ import static org.junit.jupiter.api.Assertions.*; public final class WebViewComponentsParserTests { + private static final Logger logger = LoggerFactory.getLogger(WebViewComponentsParserTests.class); + private static final String parserFileBase = String.join(File.separator, "src", "test", "parser"); private static final String parserTreeFileBase = String.join(File.separator, parserFileBase, "trees"); private static final String parseTreeFileSuffix = "_parseTree"; diff --git a/web-view-components-compiler/src/test/java/groowt/view/component/web/ast/DefaultAstBuilderTests.java b/web-view-components-compiler/src/test/java/groowt/view/component/web/ast/DefaultAstBuilderTests.java index 38e96e2..dd43642 100644 --- a/web-view-components-compiler/src/test/java/groowt/view/component/web/ast/DefaultAstBuilderTests.java +++ b/web-view-components-compiler/src/test/java/groowt/view/component/web/ast/DefaultAstBuilderTests.java @@ -2,11 +2,19 @@ package groowt.view.component.web.ast; import groowt.view.component.web.antlr.ParserUtil; import groowt.view.component.web.antlr.TokenList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.nio.file.Path; +import static groowt.view.component.web.antlr.LexerErrorKt.formatLexerError; +import static groowt.view.component.web.antlr.ParserErrorKt.formatParserError; +import static org.junit.jupiter.api.Assertions.*; + public class DefaultAstBuilderTests extends AstBuilderTests { + private static final Logger logger = LoggerFactory.getLogger(DefaultAstBuilderTests.class); + public DefaultAstBuilderTests() { super( Path.of("src", "test", "ast"), @@ -19,6 +27,20 @@ public class DefaultAstBuilderTests extends AstBuilderTests { @Override protected BuildResult buildFromSource(String source) { final var parseResult = ParserUtil.parseCompilationUnit(source); + + if (!parseResult.getLexerErrors().isEmpty()) { + parseResult.getLexerErrors().forEach(error -> { + logger.error(formatLexerError(error)); + }); + fail("There were lexer errors. See log for more information."); + } + if (!parseResult.getParserErrors().isEmpty()) { + parseResult.getParserErrors().forEach(error -> { + logger.error(formatParserError(error)); + }); + fail("There were parser errors. See log for more information."); + } + final var tokenList = new TokenList(parseResult.getTokenStream()); final var b = new DefaultAstBuilder(new DefaultNodeFactory(tokenList)); return new BuildResult(b.build(parseResult.getCompilationUnitContext()), tokenList); diff --git a/web-view-components-compiler/src/test/lexer/badTagStart.wvc b/web-view-components-compiler/src/test/lexer/badTagStart.wvc new file mode 100644 index 0000000..c9c3691 --- /dev/null +++ b/web-view-components-compiler/src/test/lexer/badTagStart.wvc @@ -0,0 +1 @@ + diff --git a/web-view-components-compiler/src/test/lexer/fragment.wvc b/web-view-components-compiler/src/test/lexer/fragment.wvc new file mode 100644 index 0000000..0d930dd --- /dev/null +++ b/web-view-components-compiler/src/test/lexer/fragment.wvc @@ -0,0 +1 @@ +<>

Hello, World!

diff --git a/web-view-components-compiler/src/test/lexer/spaceAfterSlash.wvc b/web-view-components-compiler/src/test/lexer/spaceAfterSlash.wvc new file mode 100644 index 0000000..79dd5cc --- /dev/null +++ b/web-view-components-compiler/src/test/lexer/spaceAfterSlash.wvc @@ -0,0 +1 @@ +) +4: RawText[1,9](\n) \ No newline at end of file diff --git a/web-view-components-compiler/src/test/lexer/tokens-files/complicated_tokens.txt b/web-view-components-compiler/src/test/lexer/tokens-files/complicated_tokens.txt index 06ccc0f..2511d54 100644 --- a/web-view-components-compiler/src/test/lexer/tokens-files/complicated_tokens.txt +++ b/web-view-components-compiler/src/test/lexer/tokens-files/complicated_tokens.txt @@ -33,51 +33,50 @@ 32: RawText[11,33](\n ) 33: ComponentOpen[12,13](<) 34: TypedIdentifier[12,14](Case) -35: ComponentNlws[12,18]( ) -36: AttributeIdentifier[12,19](cond) -37: Equals[12,23](=) -38: ClosureAttrValueStart[12,24]({) -39: GroovyCode[12,25](isItTrue()) -40: ClosureAttrValueEnd[12,35](}) -41: ComponentClose[12,36](>) -42: RawText[12,37](\n ) -43: ComponentOpen[13,17](<) -44: StringIdentifier[13,18](p) -45: ComponentClose[13,19](>) -46: RawText[13,20](It's true! :)) -47: ClosingComponentOpen[13,33]() -50: RawText[13,37](\n ) -51: ClosingComponentOpen[14,13]() -54: RawText[14,20](\n ) -55: ComponentOpen[15,13](<) -56: TypedIdentifier[15,14](Default) -57: ComponentClose[15,21](>) -58: RawText[15,22](\n ) -59: ComponentOpen[16,17](<) -60: StringIdentifier[16,18](p) -61: ComponentClose[16,19](>) -62: RawText[16,20](It's false... :() -63: ClosingComponentOpen[16,36]() -66: RawText[16,40](\n ) -67: ClosingComponentOpen[17,13]() -70: RawText[17,23](\n ) -71: ClosingComponentOpen[18,9]() -74: RawText[18,34](\n ) -75: ClosingComponentOpen[19,5]() -78: RawText[19,12](\n) -79: ClosingComponentOpen[20,1]() -82: RawText[20,8](\n) \ No newline at end of file +35: AttributeIdentifier[12,19](cond) +36: Equals[12,23](=) +37: ClosureAttrValueStart[12,24]({) +38: GroovyCode[12,25](isItTrue()) +39: ClosureAttrValueEnd[12,35](}) +40: ComponentClose[12,36](>) +41: RawText[12,37](\n ) +42: ComponentOpen[13,17](<) +43: StringIdentifier[13,18](p) +44: ComponentClose[13,19](>) +45: RawText[13,20](It's true! :)) +46: ClosingComponentOpen[13,33]() +49: RawText[13,37](\n ) +50: ClosingComponentOpen[14,13]() +53: RawText[14,20](\n ) +54: ComponentOpen[15,13](<) +55: TypedIdentifier[15,14](Default) +56: ComponentClose[15,21](>) +57: RawText[15,22](\n ) +58: ComponentOpen[16,17](<) +59: StringIdentifier[16,18](p) +60: ComponentClose[16,19](>) +61: RawText[16,20](It's false... :() +62: ClosingComponentOpen[16,36]() +65: RawText[16,40](\n ) +66: ClosingComponentOpen[17,13]() +69: RawText[17,23](\n ) +70: ClosingComponentOpen[18,9]() +73: RawText[18,34](\n ) +74: ClosingComponentOpen[19,5]() +77: RawText[19,12](\n) +78: ClosingComponentOpen[20,1]() +81: RawText[20,8](\n) \ No newline at end of file diff --git a/web-view-components-compiler/src/test/lexer/tokens-files/dataComponent_tokens.txt b/web-view-components-compiler/src/test/lexer/tokens-files/dataComponent_tokens.txt index ff17a63..fb38007 100644 --- a/web-view-components-compiler/src/test/lexer/tokens-files/dataComponent_tokens.txt +++ b/web-view-components-compiler/src/test/lexer/tokens-files/dataComponent_tokens.txt @@ -1,5 +1,4 @@ 0: ComponentOpen[1,1](<) 1: StringIdentifier[1,2](data-test) -2: ComponentNlws[1,11]( ) -3: ComponentSelfClose[1,12](/>) -4: RawText[1,14](\n) \ No newline at end of file +2: ComponentSelfClose[1,12](/>) +3: RawText[1,14](\n) \ No newline at end of file diff --git a/web-view-components-compiler/src/test/lexer/tokens-files/fragment_tokens.txt b/web-view-components-compiler/src/test/lexer/tokens-files/fragment_tokens.txt new file mode 100644 index 0000000..89ced11 --- /dev/null +++ b/web-view-components-compiler/src/test/lexer/tokens-files/fragment_tokens.txt @@ -0,0 +1,10 @@ +0: FragmentOpen[1,1](<>) +1: ComponentOpen[1,3](<) +2: StringIdentifier[1,4](p) +3: ComponentClose[1,5](>) +4: RawText[1,6](Hello, World!) +5: ClosingComponentOpen[1,19]() +8: FragmentClose[1,23]() +9: RawText[1,26](\n) \ No newline at end of file diff --git a/web-view-components-compiler/src/test/lexer/tokens-files/spaceAfterSlash_tokens.txt b/web-view-components-compiler/src/test/lexer/tokens-files/spaceAfterSlash_tokens.txt new file mode 100644 index 0000000..23fac26 --- /dev/null +++ b/web-view-components-compiler/src/test/lexer/tokens-files/spaceAfterSlash_tokens.txt @@ -0,0 +1 @@ +0: ClosingComponentOpen[1,1](

Hello, World!

diff --git a/web-view-components-compiler/src/test/parser/trees/complicated_parseTree.txt b/web-view-components-compiler/src/test/parser/trees/complicated_parseTree.txt index b3adfad..87453d8 100644 --- a/web-view-components-compiler/src/test/parser/trees/complicated_parseTree.txt +++ b/web-view-components-compiler/src/test/parser/trees/complicated_parseTree.txt @@ -90,7 +90,6 @@ compilationUnit[1,1..21,1] componentArgs[12,14..12,35] componentType[12,14..12,14] TypedIdentifier[12,14](Case) - ComponentNlws[12,18]( ) attr[12,19..12,35] keyValueAttr[12,19..12,35] AttributeIdentifier[12,19](cond) diff --git a/web-view-components-compiler/src/test/parser/trees/fragment_parseTree.txt b/web-view-components-compiler/src/test/parser/trees/fragment_parseTree.txt new file mode 100644 index 0000000..715fb55 --- /dev/null +++ b/web-view-components-compiler/src/test/parser/trees/fragment_parseTree.txt @@ -0,0 +1,28 @@ +compilationUnit[1,1..2,1] + body[1,1..1,26] + component[1,1..1,23] + fragmentComponent[1,1..1,23] + FragmentOpen[1,1](<>) + body[1,3..1,22] + component[1,3..1,22] + componentWithChildren[1,3..1,22] + openComponent[1,3..1,5] + ComponentOpen[1,3](<) + componentArgs[1,4..1,4] + componentType[1,4..1,4] + StringIdentifier[1,4](p) + ComponentClose[1,5](>) + body[1,6..1,6] + bodyText[1,6..1,6] + jStringBodyText[1,6..1,6] + RawText[1,6](Hello, World!) + closingComponent[1,19..1,22] + ClosingComponentOpen[1,19]() + FragmentClose[1,23]() + bodyText[1,26..1,26] + jStringBodyText[1,26..1,26] + RawText[1,26](\n) + EOF[2,1]() diff --git a/web-view-components-compiler/src/testFixtures/java/groowt/view/component/web/transpiler/GroovyTranspilerTests.java b/web-view-components-compiler/src/testFixtures/java/groowt/view/component/web/transpiler/GroovyTranspilerTests.java index 7e91138..a210980 100644 --- a/web-view-components-compiler/src/testFixtures/java/groowt/view/component/web/transpiler/GroovyTranspilerTests.java +++ b/web-view-components-compiler/src/testFixtures/java/groowt/view/component/web/transpiler/GroovyTranspilerTests.java @@ -17,12 +17,17 @@ import org.codehaus.groovy.control.CompilePhase; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import static groowt.view.component.web.antlr.LexerErrorKt.formatLexerError; import static org.junit.jupiter.api.Assertions.*; @ExtendWith(MockitoExtension.class) public abstract class GroovyTranspilerTests { + private static final Logger logger = LoggerFactory.getLogger(GroovyTranspilerTests.class); + protected final GroovyTranspiler transpiler; private final CompilationUnit groovyCompilationUnit; @@ -36,6 +41,15 @@ public abstract class GroovyTranspilerTests { private void doTranspile(String source) { final var parseResult = ParserUtil.parseCompilationUnit(source); + + if (!parseResult.getLexerErrors().isEmpty()) { + logger.error("There were lexer errors."); + parseResult.getLexerErrors().forEach(error -> { + logger.error(formatLexerError(error)); + }); + fail("There were lexer errors. See log for more information."); + } + final var tokenList = new TokenList(parseResult.getTokenStream()); final var astBuilder = new DefaultAstBuilder(new DefaultNodeFactory(tokenList)); final var cuNode = astBuilder.buildCompilationUnit(parseResult.getCompilationUnitContext()); diff --git a/web-view-components-compiler/src/tools/binTemplate.gst b/web-view-components-compiler/src/tools/binTemplate.gst index de59ea7..60e9deb 100644 --- a/web-view-components-compiler/src/tools/binTemplate.gst +++ b/web-view-components-compiler/src/tools/binTemplate.gst @@ -1,3 +1,8 @@ #/usr/bin/env bash -../gradlew toolsJar && java -cp build/libs/web-tools-0.1.0.jar:build/libs/web-views-0.1.0.jar $mainClassName "\$@" +if [ "\$1" == "--debug" ]; then + shift + gradle -q toolsJar && java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:8192 -cp build/libs/web-view-components-compiler-tools-0.1.0.jar $mainClassName "\$@" +else + gradle -q toolsJar && java -cp build/libs/web-view-components-compiler-tools-0.1.0.jar $mainClassName "\$@" +fi diff --git a/web-view-components-compiler/src/tools/groovy/groowt/view/component/web/tools/ParseTreeFileMaker.groovy b/web-view-components-compiler/src/tools/groovy/groowt/view/component/web/tools/ParseTreeFileMaker.groovy index 0e641eb..819c228 100644 --- a/web-view-components-compiler/src/tools/groovy/groowt/view/component/web/tools/ParseTreeFileMaker.groovy +++ b/web-view-components-compiler/src/tools/groovy/groowt/view/component/web/tools/ParseTreeFileMaker.groovy @@ -74,13 +74,13 @@ final class ParseTreeFileMaker extends AbstractOutputFileMaker { if (!lexerErrorListener.errors.isEmpty()) { println 'There were lexer errors.' - lexerErrorListener.errors.each { println LexerErrorKt.format(it) } + lexerErrorListener.errors.each { println LexerErrorKt.formatLexerError(it) } return null } - if (!parserErrorListener.errors.isEmpty()) { + if (!parserErrorListener.parserErrors.isEmpty()) { println 'There were parser errors.' - parserErrorListener.errors.each { println ParserErrorKt.format(it) } + parserErrorListener.parserErrors.each { println ParserErrorKt.formatParserError(it) } return null } diff --git a/web-view-components-compiler/src/tools/kotlin/groowt/view/component/web/tools/TokenizeWvc.kt b/web-view-components-compiler/src/tools/kotlin/groowt/view/component/web/tools/TokenizeWvc.kt index 6615aa1..49422b0 100644 --- a/web-view-components-compiler/src/tools/kotlin/groowt/view/component/web/tools/TokenizeWvc.kt +++ b/web-view-components-compiler/src/tools/kotlin/groowt/view/component/web/tools/TokenizeWvc.kt @@ -52,7 +52,7 @@ open class TokenizeWvc : AbstractSourceTransformerCli() { protected fun onErrors(errors: List): Boolean { println("There were errors during tokenization.") - errors.forEach { println(format(it)) } + errors.forEach { println(formatLexerError(it)) } return this.getYesNo("Do you wish to try again?", false) } @@ -99,7 +99,7 @@ open class TokenizeWvc : AbstractSourceTransformerCli() { val errorListener = LexerErrorListener() lexer.addErrorListener(errorListener) - val tokenStream = WebViewComponentsTokenStream(lexer) + val tokenStream = WebViewComponentsTokenStream(lexer, setOf(WebViewComponentsLexer.HIDDEN)) val allTokens = tokenStream.getAllTokensSkipEOF() val errors = errorListener.getErrors()