Major work on lexer/parser to help intellij plugin.

This commit is contained in:
JesseBrault0709 2024-05-22 13:18:44 +02:00
parent 1b6344dbf2
commit bfa2ec6cd7
33 changed files with 398 additions and 115 deletions

View File

@ -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<String>).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 {
}
}
}

View File

@ -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)
;
// ----------------------------------------

View File

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

View File

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

View File

@ -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]})."
}

View File

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

View File

@ -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.")
}

View File

@ -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<StackEntry> getStack();
void setLexer(Lexer lexer);
void push();
void push(OnPop onPop);
void pop();

View File

@ -14,7 +14,7 @@ class MismatchedInputParserError(
val expectedTokenTypes: Set<Int>
) : 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 ")

View File

@ -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<ParserError> = ArrayList()
private val lexerErrors: MutableList<LexerError> = ArrayList()
private val parserErrors: MutableList<ParserError> = ArrayList()
fun getErrors(): List<ParserError> = this.errors
fun getLexerErrors(): List<LexerError> = this.lexerErrors
fun getParserErrors(): List<ParserError> = 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)
}
}
}

View File

@ -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.")
}

View File

@ -18,6 +18,8 @@ data class CompilationUnitParseResult(
val lexer: WebViewComponentsLexer,
val tokenStream: WebViewComponentsTokenStream,
val parser: WebViewComponentsParser,
val lexerErrors: List<LexerError>,
val parserErrors: List<ParserError>,
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(

View File

@ -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<StackEntry> = LinkedList()
private val stack: Deque<SimpleStackEntry> = LinkedList()
private var lexer: Lexer? = null
private fun currentEntry(): StackEntry {
override fun getStack(): Deque<StackEntry> = 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)
}

View File

@ -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();

View File

@ -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<Int>
) : TokenStream {
constructor(lexer: WebViewComponentsLexer) : this(
lexer,
setOf(WebViewComponentsLexer.HIDDEN, WebViewComponentsLexer.ERROR)
)
private val tokens: MutableList<Token> = 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<Token> {
this.fill()
return this.tokens.filter { it.type != Token.EOF }
return this.getAllTokens().filter { it.type != Token.EOF }
}
private fun fill() {

View File

@ -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<Token> allTokens = tokenStream.getAllTokensSkipEOF();
final var sb = new StringBuilder();
for (int i = 0; i < allTokens.size(); i++) {

View File

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

View File

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

View File

@ -0,0 +1 @@
<bad! />

View File

@ -0,0 +1 @@
<><p>Hello, World!</p></>

View File

@ -0,0 +1 @@
</

View File

@ -0,0 +1,5 @@
0: ComponentOpen[1,1](<)
1: StringIdentifier[1,2](bad)
2: TagError[1,5](!)
3: ComponentSelfClose[1,7](/>)
4: RawText[1,9](\n)

View File

@ -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](</)
48: StringIdentifier[13,35](p)
49: ComponentClose[13,36](>)
50: RawText[13,37](\n )
51: ClosingComponentOpen[14,13](</)
52: TypedIdentifier[14,15](Case)
53: ComponentClose[14,19](>)
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](</)
64: StringIdentifier[16,38](p)
65: ComponentClose[16,39](>)
66: RawText[16,40](\n )
67: ClosingComponentOpen[17,13](</)
68: TypedIdentifier[17,15](Default)
69: ComponentClose[17,22](>)
70: RawText[17,23](\n )
71: ClosingComponentOpen[18,9](</)
72: TypedIdentifier[18,11](groowt.view.web.Select)
73: ComponentClose[18,33](>)
74: RawText[18,34](\n )
75: ClosingComponentOpen[19,5](</)
76: StringIdentifier[19,7](body)
77: ComponentClose[19,11](>)
78: RawText[19,12](\n)
79: ClosingComponentOpen[20,1](</)
80: StringIdentifier[20,3](html)
81: ComponentClose[20,7](>)
82: RawText[20,8](\n)
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](</)
47: StringIdentifier[13,35](p)
48: ComponentClose[13,36](>)
49: RawText[13,37](\n )
50: ClosingComponentOpen[14,13](</)
51: TypedIdentifier[14,15](Case)
52: ComponentClose[14,19](>)
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](</)
63: StringIdentifier[16,38](p)
64: ComponentClose[16,39](>)
65: RawText[16,40](\n )
66: ClosingComponentOpen[17,13](</)
67: TypedIdentifier[17,15](Default)
68: ComponentClose[17,22](>)
69: RawText[17,23](\n )
70: ClosingComponentOpen[18,9](</)
71: TypedIdentifier[18,11](groowt.view.web.Select)
72: ComponentClose[18,33](>)
73: RawText[18,34](\n )
74: ClosingComponentOpen[19,5](</)
75: StringIdentifier[19,7](body)
76: ComponentClose[19,11](>)
77: RawText[19,12](\n)
78: ClosingComponentOpen[20,1](</)
79: StringIdentifier[20,3](html)
80: ComponentClose[20,7](>)
81: RawText[20,8](\n)

View File

@ -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)
2: ComponentSelfClose[1,12](/>)
3: RawText[1,14](\n)

View File

@ -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](</)
6: StringIdentifier[1,21](p)
7: ComponentClose[1,22](>)
8: FragmentClose[1,23](</>)
9: RawText[1,26](\n)

View File

@ -0,0 +1 @@
0: ClosingComponentOpen[1,1](</)

View File

@ -0,0 +1 @@
<><p>Hello, World!</p></>

View File

@ -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)

View File

@ -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](</)
componentType[1,21..1,21]
StringIdentifier[1,21](p)
ComponentClose[1,22](>)
FragmentClose[1,23](</>)
bodyText[1,26..1,26]
jStringBodyText[1,26..1,26]
RawText[1,26](\n)
EOF[2,1](<EOF>)

View File

@ -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());

View File

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

View File

@ -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
}

View File

@ -52,7 +52,7 @@ open class TokenizeWvc : AbstractSourceTransformerCli() {
protected fun onErrors(errors: List<LexerError>): 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()