Lots of refactoring lexer, parser, and tools.

This commit is contained in:
JesseBrault0709 2024-05-23 16:51:13 +02:00
parent bfa2ec6cd7
commit 58d7641ece
96 changed files with 1035 additions and 554 deletions

View File

@ -3,6 +3,7 @@ package groowt.gradle.antlr;
import org.gradle.api.Plugin; import org.gradle.api.Plugin;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.tasks.Delete;
import java.io.File; import java.io.File;
@ -39,7 +40,7 @@ public final class GroowtAntlrPlugin implements Plugin<Project> {
sourceSet.getJava().srcDir(baseOutputDir); sourceSet.getJava().srcDir(baseOutputDir);
final var sourceSetTasks = antlrSourceDirectorySet.getFiles().stream() final var generateTasks = antlrSourceDirectorySet.getFiles().stream()
.filter(GroowtAntlrUtil::isAntlrFile) .filter(GroowtAntlrUtil::isAntlrFile)
.map(file -> { .map(file -> {
final var taskProvider = project.getTasks().register( final var taskProvider = project.getTasks().register(
@ -64,10 +65,21 @@ public final class GroowtAntlrPlugin implements Plugin<Project> {
}) })
.toList(); .toList();
if (!sourceSetTasks.isEmpty()) { if (!generateTasks.isEmpty()) {
final var cleanAntlr = project.getTasks().register(
sourceSet.getTaskName("clean", "Antlr"),
Delete.class,
deleteTask -> {
deleteTask.setGroup(GROOWT_ANTLR);
deleteTask.delete(baseOutputDir);
}
);
project.getTasks().register(sourceSet.getTaskName("generate", "AllAntlr"), task -> { project.getTasks().register(sourceSet.getTaskName("generate", "AllAntlr"), task -> {
task.dependsOn(sourceSetTasks);
task.setGroup(GROOWT_ANTLR); task.setGroup(GROOWT_ANTLR);
//noinspection ResultOfMethodCallIgnored
task.doFirst(first -> baseOutputDir.get().getAsFile().delete());
task.dependsOn(generateTasks);
}); });
} }
}); });

View File

@ -152,14 +152,15 @@ def toolSpec = { String name, String mainClass ->
} }
final List<ToolSpec> toolSpecs = [ final List<ToolSpec> toolSpecs = [
toolSpec('astFileMaker', 'AstFileMakerCli'), toolSpec('astFileMaker', 'AstFileMakerCli'), // deprecated
toolSpec('convertToGroovy', 'ConvertToGroovy'), toolSpec('convertToGroovy', 'ConvertToGroovy'),
toolSpec('groovyWvc', 'GroovyWvcCompiler'), toolSpec('groovyWvc', 'GroovyWvcCompiler'),
toolSpec('lexer', 'LexerTool'), toolSpec('lexer', 'LexerTool'), // deprecated
toolSpec('parser', 'ParserTool'), toolSpec('parser', 'ParserTool'), // deprecated
toolSpec('parseTreeFileMaker', 'ParseTreeFileMakerCli'), toolSpec('parseTreeFileMaker', 'ParseTreeFileMakerCli'), // deprecated
toolSpec('parseWvc', 'ParseWvc'),
toolSpec('tokenizeWvc', 'TokenizeWvc'), toolSpec('tokenizeWvc', 'TokenizeWvc'),
toolSpec('tokensFileMaker', 'TokensFileMakerCli') toolSpec('tokensFileMaker', 'TokensFileMakerCli') // deprecated
] ]
toolSpecs.each { spec -> toolSpecs.each { spec ->

View File

@ -0,0 +1,10 @@
#!/usr/bin/env bash
ARGS="-v -d src/test/lexer/tokens-files -s _tokens -e .txt"
if [ "$1" == "--debug" ]; then
shift
bin/tokenizeWvc --debug $ARGS "$@"
else
bin/tokenizeWvc $ARGS "$@"
fi

View File

@ -0,0 +1,10 @@
#!/usr/bin/env bash
ARGS="-v -d src/test/parser/parse-tree-files -s _parseTree -e .txt"
if [ "$1" == "--debug" ]; then
shift
bin/parseWvc --debug $ARGS "$@"
else
bin/parseWvc $ARGS "$@"
fi

View File

@ -8,12 +8,13 @@ options {
tokens { tokens {
PreambleBreak, PreambleBreak,
ComponentNlws,
GroovyCode, GroovyCode,
GStringAttrValueEnd, GStringAttrValueEnd,
JStringAttrValueEnd, JStringAttrValueEnd,
ClosureAttrValueEnd, ClosureAttrValueEnd,
DollarScriptletClose DollarScriptletClose,
Nlws,
ErrorChar
} }
channels { channels {
@ -88,14 +89,12 @@ channels {
DollarSlashyStringClosureStart DollarSlashyStringClosureStart
); );
public WebViewComponentsLexerBase() { public WebViewComponentsLexerBase() {}
_interp = new LexerATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache);
}
private void onPreambleClose() { private void onPreambleClose() {
this.setType(PreambleBreak); this.setType(PreambleBreak);
this.exitPreamble(); this.exitPreamble();
this.popMode(); this.mode(MAIN);
} }
private void onGStringClosure() { private void onGStringClosure() {
@ -116,10 +115,21 @@ channels {
// DEFAULT_MODE // DEFAULT_MODE
PreambleOpen PreambleOpen
: THREE_DASH ( NL | WS+ )? { this.canPreamble() }? { this.enterPreamble(); } : THREE_DASH ( NL | WS+ )? { this.enterPreamble(); }
-> type(PreambleBreak), pushMode(GROOVY_CODE) -> type(PreambleBreak), pushMode(GROOVY_CODE)
; ;
NotPreambleOpen
: .
{
this.rollbackOne();
this.setCanPreamble(false);
} -> skip, mode(MAIN)
;
// ----------------------------------------
mode MAIN;
ComponentOpen ComponentOpen
: LT { !isAnyOf(this.getNextChar(), '/', '>') }? -> pushMode(TAG_START) : LT { !isAnyOf(this.getNextChar(), '/', '>') }? -> pushMode(TAG_START)
; ;
@ -156,25 +166,39 @@ DollarScriptletOpen
; ;
DollarReferenceStart DollarReferenceStart
: DOLLAR { isIdentifierStartChar(this.getNextChar()) }? -> pushMode(IN_G_STRING_PATH) : DOLLAR { isGStringIdentifierStartChar(this.getNextChar()) }? -> pushMode(IN_G_STRING_PATH)
; ;
QuestionTag QuestionTagOpen
: LT QUESTION .*? QUESTION GT : LT QUESTION
; ;
HtmlComment QuestionTagClose
: LT BANG TWO_DASH .*? TWO_DASH GT : QUESTION GT
;
HtmlCommentOpen
: LT BANG TWO_DASH
;
HtmlCommentClose
: TWO_DASH GT
; ;
RawText RawText
: ( ~[-<$] // n.b.: LT cannot be escaped, only via &lt; : ( ~[-<$?]
| MINUS { !this.isNext("--") }? | MINUS { !this.isNext("->") }?
| DOLLAR { !this.isNext('{') && !isIdentifierStartChar(this.getNextChar()) }? | LT { canFollowLessThan(this.getNextCharAsString()) }?
| LT BANG { !this.isNext("--") }? | LT BANG { !this.isNext("--") }?
| DOLLAR { !(this.isNext('{') || isIdentifierStartChar(this.getNextChar())) }?
| QUESTION { !this.isNext('>') }?
)+ )+
; ;
MainError
: . -> type(ErrorChar), channel(ERROR)
;
// ---------------------------------------- // ----------------------------------------
mode TAG_START; mode TAG_START;
@ -227,11 +251,11 @@ StringIdentifierChar
; ;
TagStartNlws TagStartNlws
: NLWS+ -> type(ComponentNlws), channel(HIDDEN) : NLWS+ -> type(Nlws), channel(HIDDEN)
; ;
TagStartError TagStartError
: . -> channel(ERROR) : . -> type(ErrorChar), channel(ERROR)
; ;
// ---------------------------------------- // ----------------------------------------
@ -276,7 +300,7 @@ JStringAttrValueStart
; ;
ClosureAttrValueStart ClosureAttrValueStart
: LEFT_CURLY InTagNlws? { !this.isNext('<') }? : LEFT_CURLY { !this.isNextIgnoreNlws('<') }?
{ {
this.curlies.push(() -> { this.curlies.push(() -> {
this.setType(ClosureAttrValueEnd); this.setType(ClosureAttrValueEnd);
@ -296,7 +320,7 @@ ComponentAttrValueEnd
; ;
InTagNlws InTagNlws
: NLWS+ -> type(ComponentNlws), channel(HIDDEN) : NLWS+ -> type(Nlws), channel(HIDDEN)
; ;
TagError TagError
@ -307,10 +331,7 @@ TagError
mode GROOVY_CODE; mode GROOVY_CODE;
PreambleClose PreambleClose
: NL? THREE_DASH ( NL | WS+ )? { this.inPreamble() }? : THREE_DASH { this.inPreamble() && this.getCharPositionInLine() == 3 }? { this.onPreambleClose(); }
{
this.onPreambleClose();
}
; ;
ScriptletClose ScriptletClose
@ -318,10 +339,9 @@ ScriptletClose
; ;
GroovyCodeChars GroovyCodeChars
: ( ~[/\n\r\-$%(){}'"] : ( ~[-/$%(){}'"]
| FS { !isAnyOf(this.getNextChar(), '/', '*') }?
| NL { !this.inPreamble() || !this.isNext("---") }?
| MINUS { !(this.getCharPositionInLine() == 1 && this.isNext("--")) }? | MINUS { !(this.getCharPositionInLine() == 1 && this.isNext("--")) }?
| FS { !isAnyOf(this.getNextChar(), '/', '*') }?
| DOLLAR { !this.isNext('/') }? | DOLLAR { !this.isNext('/') }?
| PERCENT { !this.isNext('>') }? | PERCENT { !this.isNext('>') }?
)+ )+
@ -418,7 +438,7 @@ LineCommentEnd
mode IN_STAR_COMMENT; mode IN_STAR_COMMENT;
StarCommentChars StarCommentChars
: ~'*'+ : ~'*' | ( '*' { !this.isNext('/') }? )
; ;
StarCommentEnd StarCommentEnd
@ -449,11 +469,12 @@ GStringText
: ( ~[\n\r"$] : ( ~[\n\r"$]
| BS DQ | BS DQ
| BS DOLLAR | BS DOLLAR
| DOLLAR { !isGStringIdentifierStartChar(this.getNextChar()) }?
)+ )+
; ;
GStringDollarValueStart GStringDollarValueStart
: DOLLAR { !this.isNext('{') }? -> pushMode(IN_G_STRING_PATH) : DOLLAR { !this.isNext('{') && isGStringIdentifierStartChar(this.getNextChar()) }? -> pushMode(IN_G_STRING_PATH)
; ;
GStringClosureStart GStringClosureStart
@ -521,13 +542,14 @@ GStringPathEnd
yield new StringContinueEndSpec(); yield new StringContinueEndSpec();
} }
} }
case DEFAULT_MODE -> new StringContinueEndSpec(); case MAIN -> new StringContinueEndSpec();
default -> throw new IllegalStateException("not a valid gString context: " + this.getModeName(this.peekMode(1))); default -> throw new IllegalStateException("not a valid gString context: " + this.getModeName(this.peekMode(1)));
}; };
switch (endSpec) { switch (endSpec) {
case StringContinueEndSpec ignored -> { case StringContinueEndSpec ignored -> {
this.popMode(); this.popMode();
this.rollbackOne(); this.rollbackOne(true);
this.skip();
} }
case StringClosingEndSpec closingEndSpec -> { case StringClosingEndSpec closingEndSpec -> {
this.setType(closingEndSpec.type()); this.setType(closingEndSpec.type());
@ -550,21 +572,21 @@ GStringIdentifierChar
// ---------------------------------------- // ----------------------------------------
mode IN_TRIPLE_J_STRING; mode IN_TRIPLE_J_STRING;
// TODO: check for unescaped SQ, I think groovy allows them
TripleJStringContent TripleJStringContent
: ( ~['] | BS SQ )+ : ( ~[']
| SQ { !(this.isNext("''") && this._input.LA(3) != '\'') }?
)+
; ;
TripleJStringEnd TripleJStringEnd
: SQ SQ SQ -> popMode : SQ SQ SQ { !this.isNext('\'') }? -> popMode
; ;
// ---------------------------------------- // ----------------------------------------
mode IN_TRIPLE_G_STRING; mode IN_TRIPLE_G_STRING;
// TODO: check for unescaped DQ, I think groovy allows them
TripleGStringDollarValueStart TripleGStringDollarValueStart
: DOLLAR { !this.isNext('{') }? -> pushMode(IN_G_STRING_PATH) : DOLLAR { isGStringIdentifierStartChar(this.getNextChar()) }? -> pushMode(IN_G_STRING_PATH)
; ;
TripleGStringClosureStart TripleGStringClosureStart
@ -572,11 +594,15 @@ TripleGStringClosureStart
; ;
TripleGStringText TripleGStringText
: ( ~["$] | BS DQ | BS DOLLAR )+ : ( ~["$]
| DQ { !(this.isNext("\"\"") && this._input.LA(3) != '"') }?
| BS DOLLAR
| DOLLAR { !isGStringIdentifierStartChar(this.getNextChar()) }?
)+
; ;
TripleGStringEnd TripleGStringEnd
: DQ DQ DQ -> popMode : DQ DQ DQ { !this.isNext('"') }? -> popMode
; ;
// ---------------------------------------- // ----------------------------------------
@ -587,8 +613,9 @@ ParenthesesSlashyStringText
; ;
ParenthesesSlashyStringDollarValueStart ParenthesesSlashyStringDollarValueStart
: DOLLAR { !this.isNext('{') }? -> pushMode(IN_G_STRING_PATH) : DOLLAR { isGStringIdentifierStartChar(this.getNextChar()) }? -> pushMode(IN_G_STRING_PATH)
; ;
ParenthesesSlashyStringClosureStart ParenthesesSlashyStringClosureStart
: DOLLAR LEFT_CURLY { this.onGStringClosure(); } : DOLLAR LEFT_CURLY { this.onGStringClosure(); }
; ;
@ -603,13 +630,14 @@ mode IN_DOLLAR_SLASHY_STRING;
DollarSlashyStringText DollarSlashyStringText
: ( ~[$/] : ( ~[$/]
| DOLLAR DOLLAR | DOLLAR DOLLAR
| DOLLAR FS | DOLLAR { !isGStringIdentifierStartChar(this.getNextChar()) }?
| FS DOLLAR DOLLAR | FS { !this.isNext('$') }?
)+ )+
; ;
DollarSlashyStringDollarValueStart DollarSlashyStringDollarValueStart
: DOLLAR { !isAnyOf(this.getNextChar(), '/', '$', '{') }? -> pushMode(IN_G_STRING_PATH) : DOLLAR { isGStringIdentifierStartChar(this.getNextChar()) }?
-> pushMode(IN_G_STRING_PATH)
; ;
DollarSlashyStringClosureStart DollarSlashyStringClosureStart

View File

@ -17,18 +17,22 @@ body
; ;
bodyText bodyText
: gStringBodyText | jStringBodyText : ( questionTag | htmlComment | text | bodyTextGroovyElement )+
; ;
gStringBodyText questionTag
: jStringBodyText? ( gStringBodyTextGroovyElement jStringBodyText? )+ : QuestionTagOpen ( text | bodyTextGroovyElement )* QuestionTagClose
; ;
jStringBodyText htmlComment
: ( QuestionTag | HtmlComment | RawText )+ : HtmlCommentOpen ( text | bodyTextGroovyElement )* HtmlCommentClose
; ;
gStringBodyTextGroovyElement text
: RawText
;
bodyTextGroovyElement
: plainScriptlet | equalsScriptlet | dollarScriptlet | dollarReference : plainScriptlet | equalsScriptlet | dollarScriptlet | dollarReference
; ;

View File

@ -1,5 +1,6 @@
package groowt.view.component.web.antlr; package groowt.view.component.web.antlr;
import groovyjarjarantlr4.runtime.Token;
import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.Lexer; import org.antlr.v4.runtime.Lexer;
import org.antlr.v4.runtime.atn.ATN; import org.antlr.v4.runtime.atn.ATN;
@ -15,6 +16,7 @@ import java.util.LinkedList;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static groowt.view.component.web.antlr.LexerSemanticPredicates.isAnyOf;
import static groowt.view.component.web.antlr.TokenUtil.escapeChars; import static groowt.view.component.web.antlr.TokenUtil.escapeChars;
public abstract class AbstractWebViewComponentsLexer extends Lexer { public abstract class AbstractWebViewComponentsLexer extends Lexer {
@ -30,11 +32,13 @@ public abstract class AbstractWebViewComponentsLexer extends Lexer {
super(recognizer, atn, decisionToDFA, sharedContextCache); super(recognizer, atn, decisionToDFA, sharedContextCache);
} }
public void resetAcceptPosition(CharStream input, int index, int line, int charPositionInLine) { public void resetAcceptPosition(CharStream input, int index, int line, int charPositionInLine, boolean consume) {
input.seek(index); input.seek(index);
this.line = line; this.line = line;
this.charPositionInLine = charPositionInLine; this.charPositionInLine = charPositionInLine;
this.consume(input); if (consume) {
this.consume(input);
}
} }
} }
@ -87,7 +91,7 @@ public abstract class AbstractWebViewComponentsLexer extends Lexer {
return this.logger; return this.logger;
} }
public boolean isCanPreamble() { public boolean canPreamble() {
return this.canPreamble; return this.canPreamble;
} }
@ -95,7 +99,7 @@ public abstract class AbstractWebViewComponentsLexer extends Lexer {
this.canPreamble = canPreamble; this.canPreamble = canPreamble;
} }
public boolean isInPreamble() { public boolean inPreamble() {
return this.inPreamble; return this.inPreamble;
} }
@ -103,7 +107,7 @@ public abstract class AbstractWebViewComponentsLexer extends Lexer {
this.inPreamble = inPreamble; this.inPreamble = inPreamble;
} }
public boolean isInConstructor() { public boolean inConstructor() {
return this.inConstructor; return this.inConstructor;
} }
@ -176,19 +180,11 @@ public abstract class AbstractWebViewComponentsLexer extends Lexer {
} }
} }
protected boolean canPreamble() {
return this.canPreamble;
}
protected void enterPreamble() { protected void enterPreamble() {
this.inPreamble = true; this.inPreamble = true;
this.canPreamble = false; this.canPreamble = false;
} }
protected boolean inPreamble() {
return this.inPreamble;
}
protected void exitPreamble() { protected void exitPreamble() {
this.inPreamble = false; this.inPreamble = false;
} }
@ -199,10 +195,6 @@ public abstract class AbstractWebViewComponentsLexer extends Lexer {
this.inConstructor = true; this.inConstructor = true;
} }
protected boolean inConstructor() {
return this.inConstructor;
}
protected boolean canExitConstructor() { protected boolean canExitConstructor() {
return this.inConstructor && this.parentheses.getStackSize() == 1 && this.parentheses.isLast(); return this.inConstructor && this.parentheses.getStackSize() == 1 && this.parentheses.isLast();
} }
@ -220,6 +212,10 @@ public abstract class AbstractWebViewComponentsLexer extends Lexer {
return b.toString(); return b.toString();
} }
protected String getNextCharAsString() {
return Character.toString((char) this.getNextChar());
}
protected int getCurrentChar() { protected int getCurrentChar() {
return this._input.LA(-1); return this._input.LA(-1);
} }
@ -236,6 +232,16 @@ public abstract class AbstractWebViewComponentsLexer extends Lexer {
return this.getNextCharsAsString(test.length()).equals(test); return this.getNextCharsAsString(test.length()).equals(test);
} }
protected boolean isNextIgnoreNlws(char test) {
for (int i = 1; this._input.LA(i) != Token.EOF; i++) {
final char subject = (char) this._input.LA(i);
if (!isAnyOf(subject, ' ', '\t', '\n', '\r')) {
return subject == test;
}
}
return false;
}
@Override @Override
public final LexerATNSimulator getInterpreter() { public final LexerATNSimulator getInterpreter() {
return this._interp; return this._interp;
@ -244,11 +250,16 @@ public abstract class AbstractWebViewComponentsLexer extends Lexer {
protected abstract PositionAdjustingLexerATNSimulator getPositionAdjustingInterpreter(); protected abstract PositionAdjustingLexerATNSimulator getPositionAdjustingInterpreter();
protected void rollbackOne() { protected void rollbackOne() {
this.rollbackOne(false);
}
protected void rollbackOne(boolean consume) {
this.getPositionAdjustingInterpreter().resetAcceptPosition( this.getPositionAdjustingInterpreter().resetAcceptPosition(
this._input, this._input,
this._tokenStartCharIndex - 1, Math.max(this._tokenStartCharIndex - 1, 0),
this._tokenStartLine, this._tokenStartLine,
this._tokenStartCharIndex - 1 Math.max(this._tokenStartCharIndex - 1, 0),
consume
); );
} }

View File

@ -4,6 +4,7 @@ package groowt.view.component.web.antlr
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.lang.invoke.MethodHandles import java.lang.invoke.MethodHandles
import java.util.regex.Pattern
private val logger: Logger = LoggerFactory.getLogger( private val logger: Logger = LoggerFactory.getLogger(
MethodHandles.lookup().lookupClass() MethodHandles.lookup().lookupClass()
@ -66,6 +67,13 @@ fun canFollowGStringOpening(nextTwo: String): Boolean {
return result return result
} }
private val notAllowedAfterLessThan = Pattern.compile("[\\p{L}%/>]")
fun canFollowLessThan(subject: String): Boolean {
val m = notAllowedAfterLessThan.matcher(subject)
return !m.matches()
}
fun isIdentifierStartChar(c: Char): Boolean = Character.isJavaIdentifierStart(c) fun isIdentifierStartChar(c: Char): Boolean = Character.isJavaIdentifierStart(c)
fun isIdentifierStartChar(subject: Int) = isIdentifierStartChar(subject.toChar()) fun isIdentifierStartChar(subject: Int) = isIdentifierStartChar(subject.toChar())

View File

@ -5,25 +5,73 @@ import groowt.view.component.web.util.excerpt
import org.antlr.v4.runtime.ParserRuleContext import org.antlr.v4.runtime.ParserRuleContext
import org.antlr.v4.runtime.Token import org.antlr.v4.runtime.Token
open class ParserError(val type: ParserErrorType, val offending: Token, val context: ParserRuleContext) open class ParserError {
val type: ParserErrorType
val offendingToken: Token?
val offendingSymbol: Any?
val msg: String?
val sourcePosition: SourcePosition
val context: ParserRuleContext
constructor(
type: ParserErrorType,
offendingToken: Token?,
sourcePosition: SourcePosition,
context: ParserRuleContext
) {
this.type = type
this.offendingToken = offendingToken
this.offendingSymbol = offendingToken
this.msg = null;
this.sourcePosition = sourcePosition
this.context = context
}
constructor(
type: ParserErrorType,
offendingSymbol: Any?,
msg: String,
sourcePosition: SourcePosition,
context: ParserRuleContext
) {
this.type = type
this.offendingToken = null
this.offendingSymbol = offendingSymbol
this.msg = msg
this.sourcePosition = sourcePosition
this.context = context
}
}
class MismatchedInputParserError( class MismatchedInputParserError(
type: ParserErrorType, type: ParserErrorType,
offending: Token, offendingToken: Token,
sourcePosition: SourcePosition,
context: ParserRuleContext, context: ParserRuleContext,
val expectedTokenTypes: Set<Int> val expectedTokenTypes: Set<Int>
) : ParserError(type, offending, context) ) : ParserError(type, offendingToken, sourcePosition, context)
fun formatParserError(error: ParserError): String { fun formatParserError(error: ParserError): String {
val sb = StringBuilder() val sb = StringBuilder()
val sourcePosition = SourcePosition.fromStartOfToken(error.offending)
sb.append("At ") sb.append("At ")
.append(sourcePosition.toStringLong()) .append(error.sourcePosition.toStringLong())
.append(": ") .append(": ")
.append(error.type.message) if (error.type != ParserErrorType.UNKNOWN) {
.append(" Offending token: ") sb.append(error.type.message)
.append(formatTokenForError(error.offending)) } else if (error.msg != null) {
.append(". ") sb.append(error.msg + ".")
} else {
sb.append("Parser unknown error.")
}
if (error.offendingToken != null) {
sb.append(" Offending token: ")
.append(formatTokenForError(error.offendingToken))
.append(". ")
} else if (error.offendingSymbol != null) {
sb.append(" Offending symbol: ${error.offendingSymbol}. ")
}
if (error is MismatchedInputParserError) { if (error is MismatchedInputParserError) {
sb.append("Expected any of: ") sb.append("Expected any of: ")
.append(formatExpected(error.expectedTokenTypes)) .append(formatExpected(error.expectedTokenTypes))

View File

@ -18,7 +18,7 @@ class ParserErrorListener : BaseErrorListener() {
line: Int, line: Int,
charPositionInLine: Int, charPositionInLine: Int,
msg: String, msg: String,
e: RecognitionException e: RecognitionException?
) { ) {
val parser = recognizer as WebViewComponentsParser val parser = recognizer as WebViewComponentsParser
when (e) { when (e) {
@ -30,20 +30,41 @@ class ParserErrorListener : BaseErrorListener() {
this.lexerErrors.add(error) this.lexerErrors.add(error)
} }
is NoViableAltException -> { is NoViableAltException -> {
val error = ParserError(ParserErrorType.NO_VIABLE_ALTERNATIVE, e.offendingToken, parser.context) val error = ParserError(
ParserErrorType.NO_VIABLE_ALTERNATIVE,
e.offendingToken,
SourcePosition.fromStartOfToken(e.offendingToken),
parser.context
)
parserErrors.add(error) parserErrors.add(error)
} }
is InputMismatchException -> { is InputMismatchException -> {
val error = MismatchedInputParserError( val error = MismatchedInputParserError(
ParserErrorType.INPUT_MISMATCH, ParserErrorType.INPUT_MISMATCH,
e.offendingToken, e.offendingToken,
SourcePosition.fromStartOfToken(e.offendingToken),
parser.context, parser.context,
e.expectedTokens.toSet() e.expectedTokens.toSet()
) )
parserErrors.add(error) parserErrors.add(error)
} }
is FailedPredicateException -> { is FailedPredicateException -> {
val error = ParserError(ParserErrorType.FAILED_PREDICATE, e.offendingToken, parser.context) val error = ParserError(
ParserErrorType.FAILED_PREDICATE,
e.offendingToken,
SourcePosition.fromStartOfToken(e.offendingToken),
parser.context
)
parserErrors.add(error)
}
else -> {
val error = ParserError(
ParserErrorType.UNKNOWN,
offendingSymbol,
msg,
SourcePosition(line, charPositionInLine + 1),
parser.context
)
parserErrors.add(error) parserErrors.add(error)
} }
} }

View File

@ -3,5 +3,6 @@ package groowt.view.component.web.antlr
enum class ParserErrorType(val message: String) { enum class ParserErrorType(val message: String) {
NO_VIABLE_ALTERNATIVE("Parser has no viable alternative."), NO_VIABLE_ALTERNATIVE("Parser has no viable alternative."),
INPUT_MISMATCH("Parser input mismatch."), INPUT_MISMATCH("Parser input mismatch."),
FAILED_PREDICATE("Parser input failed predicate.") FAILED_PREDICATE("Parser input failed predicate."),
UNKNOWN("Parser unknown error.")
} }

View File

@ -2,6 +2,7 @@
package groowt.view.component.web.antlr package groowt.view.component.web.antlr
import groowt.view.component.web.antlr.WebViewComponentsParser.CompilationUnitContext import groowt.view.component.web.antlr.WebViewComponentsParser.CompilationUnitContext
import groowt.view.component.web.util.SourcePosition
import org.antlr.v4.runtime.* import org.antlr.v4.runtime.*
import org.antlr.v4.runtime.tree.ErrorNode import org.antlr.v4.runtime.tree.ErrorNode
import org.antlr.v4.runtime.tree.ParseTree import org.antlr.v4.runtime.tree.ParseTree
@ -85,7 +86,7 @@ private fun formatForError(parser: Parser, tree: ParseTree): String {
} }
fun formatTree(parser: Parser, tree: Tree, colors: Boolean, consumer: Consumer<String>): Unit = fun formatTree(parser: Parser, tree: Tree, colors: Boolean, consumer: Consumer<String>): Unit =
consumer.accept(formatTree(parser, tree, colors).toString()) consumer.accept(formatTree(parser, tree, colors))
fun formatTree(parser: Parser, tree: Tree, colors: Boolean = true): String = fun formatTree(parser: Parser, tree: Tree, colors: Boolean = true): String =
doFormatTree(parser, tree, colors, 0, " ", StringBuilder()).toString() doFormatTree(parser, tree, colors, 0, " ", StringBuilder()).toString()
@ -109,12 +110,10 @@ private fun formatBasicInfo(parser: Parser, tree: Tree, sb: StringBuilder) {
when (tree) { when (tree) {
is ParserRuleContext -> { is ParserRuleContext -> {
sb.append(parser.ruleNames[tree.ruleIndex]) sb.append(parser.ruleNames[tree.ruleIndex])
.append( val start = SourcePosition.fromStartOfToken(tree.start)
"[${tree.start.line},${tree.start.charPositionInLine + 1}.." val end = SourcePosition.fromEndOfToken(tree.stop)
+ "${tree.stop.line},${tree.stop.charPositionInLine + 1}]" sb.append("[${start.line},${start.column}..${end.line},${end.column}]")
)
} }
is TerminalNode -> { is TerminalNode -> {
sb.append(parser.vocabulary.getDisplayName(tree.symbol.type)) sb.append(parser.vocabulary.getDisplayName(tree.symbol.type))
.append("[${tree.symbol.line},${tree.symbol.charPositionInLine + 1}]") .append("[${tree.symbol.line},${tree.symbol.charPositionInLine + 1}]")
@ -139,14 +138,14 @@ private fun doFormatTree(
sb.append(ansi().fgRed()) sb.append(ansi().fgRed())
} }
} }
formatBasicInfo (parser, tree, sb) formatBasicInfo(parser, tree, sb)
if (e != null) { if (e != null) {
sb.append(": Exception: ${e.javaClass.simpleName}(${escapeChars(tree.text)})") sb.append(": Exception: ${e.javaClass.simpleName}(${escapeChars(tree.text)})")
if (colors) { if (colors) {
sb.append(ansi().reset()) sb.append(ansi().reset())
} }
} }
sb . append ("\n") sb.append ("\n")
var i = 0 var i = 0
while (i < tree.childCount) { while (i < tree.childCount) {
doFormatTree(parser, tree.getChild(i), colors, indentTimes + 1, indentText, sb) doFormatTree(parser, tree.getChild(i), colors, indentTimes + 1, indentText, sb)

View File

@ -11,6 +11,7 @@ public class WebViewComponentsLexer extends WebViewComponentsLexerBase {
public WebViewComponentsLexer() { public WebViewComponentsLexer() {
super(); super();
this._interp = new PositionAdjustingLexerATNSimulator(this, _ATN, _decisionToDFA, _sharedContextCache);
} }
@Override @Override

View File

@ -14,7 +14,7 @@ class WebViewComponentsTokenStream(
constructor(lexer: WebViewComponentsLexer) : this( constructor(lexer: WebViewComponentsLexer) : this(
lexer, lexer,
setOf(WebViewComponentsLexer.HIDDEN, WebViewComponentsLexer.ERROR) setOf(WebViewComponentsLexer.HIDDEN)
) )
private val tokens: MutableList<Token> = ArrayList() private val tokens: MutableList<Token> = ArrayList()

View File

@ -4,6 +4,7 @@ import groowt.view.component.web.WebViewComponentBugError;
import groowt.view.component.web.antlr.MergedGroovyCodeToken; import groowt.view.component.web.antlr.MergedGroovyCodeToken;
import groowt.view.component.web.antlr.TokenUtil; import groowt.view.component.web.antlr.TokenUtil;
import groowt.view.component.web.antlr.WebViewComponentsParser; import groowt.view.component.web.antlr.WebViewComponentsParser;
import groowt.view.component.web.antlr.WebViewComponentsParser.BodyTextContext;
import groowt.view.component.web.antlr.WebViewComponentsParserBaseVisitor; import groowt.view.component.web.antlr.WebViewComponentsParserBaseVisitor;
import groowt.view.component.web.ast.node.*; import groowt.view.component.web.ast.node.*;
import groowt.view.component.web.util.TokenRange; import groowt.view.component.web.util.TokenRange;
@ -18,7 +19,6 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.regex.Pattern;
public class DefaultAstBuilderVisitor extends WebViewComponentsParserBaseVisitor<Node> { public class DefaultAstBuilderVisitor extends WebViewComponentsParserBaseVisitor<Node> {
@ -73,18 +73,10 @@ public class DefaultAstBuilderVisitor extends WebViewComponentsParserBaseVisitor
return ctx.getChild(0).accept(this); return ctx.getChild(0).accept(this);
} }
protected <R extends Node> R getSingleChildAs(ParserRuleContext ctx, Class<R> type) {
return type.cast(this.getSingleChild(ctx));
}
protected TokenRange getTokenRange(ParserRuleContext ctx) { protected TokenRange getTokenRange(ParserRuleContext ctx) {
return TokenRange.of(ctx.start, ctx.stop); return TokenRange.of(ctx.start, ctx.stop);
} }
protected TerminalNode getSingleChildTerminalNode(WebViewComponentsParser.JStringBodyTextContext ctx) {
return ctx.getChild(TerminalNode.class, 0);
}
@Override @Override
public Node visitCompilationUnit(WebViewComponentsParser.CompilationUnitContext ctx) { public Node visitCompilationUnit(WebViewComponentsParser.CompilationUnitContext ctx) {
final PreambleNode preamble = this.getSingleAs(ctx.preamble(), PreambleNode.class); final PreambleNode preamble = this.getSingleAs(ctx.preamble(), PreambleNode.class);
@ -119,37 +111,57 @@ public class DefaultAstBuilderVisitor extends WebViewComponentsParserBaseVisitor
} }
@Override @Override
public Node visitBodyText(WebViewComponentsParser.BodyTextContext ctx) { public @Nullable Node visitBodyText(BodyTextContext ctx) {
return this.getSingleChild(ctx); final List<BodyTextChild> children = new ArrayList<>();
}
@Override
public Node visitGStringBodyText(WebViewComponentsParser.GStringBodyTextContext ctx) {
final List<Node> children = new ArrayList<>();
for (final var child : ctx.children) { for (final var child : ctx.children) {
final @Nullable Node childResult = child.accept(this); final @Nullable Node childResult = child.accept(this);
if (childResult != null) { if (childResult != null) {
children.add(childResult); children.add((BodyTextChild) childResult);
} }
} }
return this.nodeFactory.gStringBodyTextNode(this.getTokenRange(ctx), children); if (children.isEmpty()) {
return null;
} else {
return this.nodeFactory.bodyTextNode(this.getTokenRange(ctx), children);
}
} }
@Override @Override
public @Nullable Node visitJStringBodyText(WebViewComponentsParser.JStringBodyTextContext ctx) { public @Nullable Node visitQuestionTag(WebViewComponentsParser.QuestionTagContext ctx) {
final String text = ctx.getText(); final List<QuestionTagChild> children = new ArrayList<>();
if (isNotBlankNotEmpty(text)) { for (final var child : ctx.children) {
return this.nodeFactory.jStringBodyTextNode( final @Nullable Node childResult = child.accept(this);
this.getTokenRange(ctx), if (childResult != null) {
text children.add((QuestionTagChild) childResult);
); }
}
return this.nodeFactory.questionTagNode(this.getTokenRange(ctx), children);
}
@Override
public Node visitHtmlComment(WebViewComponentsParser.HtmlCommentContext ctx) {
final List<HtmlCommentChild> children = new ArrayList<>();
for (final var child : ctx.children) {
final @Nullable Node childResult = child.accept(this);
if (childResult != null) {
children.add((HtmlCommentChild) childResult);
}
}
return this.nodeFactory.htmlCommentNode(this.getTokenRange(ctx), children);
}
@Override
public @Nullable Node visitText(WebViewComponentsParser.TextContext ctx) {
final String content = ctx.getText();
if (isNotBlankNotEmpty(content)) {
return this.nodeFactory.textNode(this.getTokenRange(ctx), content);
} else { } else {
return null; return null;
} }
} }
@Override @Override
public Node visitGStringBodyTextGroovyElement(WebViewComponentsParser.GStringBodyTextGroovyElementContext ctx) { public Node visitBodyTextGroovyElement(WebViewComponentsParser.BodyTextGroovyElementContext ctx) {
return this.getSingleChild(ctx); return this.getSingleChild(ctx);
} }
@ -208,16 +220,6 @@ public class DefaultAstBuilderVisitor extends WebViewComponentsParserBaseVisitor
return this.nodeFactory.componentArgsNode(this.getTokenRange(ctx), typeNode, constructorNode, attrNodes); return this.nodeFactory.componentArgsNode(this.getTokenRange(ctx), typeNode, constructorNode, attrNodes);
} }
private static final Pattern lowercaseLetterPattern = Pattern.compile("\\p{Ll}");
protected boolean startsWithLowercaseLetter(String subject) {
if (subject.isEmpty()) {
throw new IllegalArgumentException(
"Cannot test for starting lowercase letter when the subject length is 0; given: " + subject);
}
return lowercaseLetterPattern.matcher(subject.substring(0, 1)).matches();
}
@Override @Override
public Node visitComponentType(WebViewComponentsParser.ComponentTypeContext ctx) { public Node visitComponentType(WebViewComponentsParser.ComponentTypeContext ctx) {
final var typedIdentifier = ctx.TypedIdentifier(); final var typedIdentifier = ctx.TypedIdentifier();
@ -291,7 +293,6 @@ public class DefaultAstBuilderVisitor extends WebViewComponentsParserBaseVisitor
if (groovyCode != null) { if (groovyCode != null) {
final MergedGroovyCodeToken groovyCodeToken = (MergedGroovyCodeToken) groovyCode.getSymbol(); final MergedGroovyCodeToken groovyCodeToken = (MergedGroovyCodeToken) groovyCode.getSymbol();
if (canBeGString(groovyCodeToken.getOriginals())) { if (canBeGString(groovyCodeToken.getOriginals())) {
// TODO: we need to set the appropriate type: slashy, dollar slashy, etc.
return this.nodeFactory.gStringValueNode(ctxTokenRange, groovyCodeToken.getTokenIndex()); return this.nodeFactory.gStringValueNode(ctxTokenRange, groovyCodeToken.getTokenIndex());
} else { } else {
return this.nodeFactory.jStringValueNode(ctxTokenRange, groovyCode.getText()); return this.nodeFactory.jStringValueNode(ctxTokenRange, groovyCode.getText());
@ -337,7 +338,7 @@ public class DefaultAstBuilderVisitor extends WebViewComponentsParserBaseVisitor
public @Nullable Node visitEqualsScriptlet(WebViewComponentsParser.EqualsScriptletContext ctx) { public @Nullable Node visitEqualsScriptlet(WebViewComponentsParser.EqualsScriptletContext ctx) {
final TerminalNode groovyCode = ctx.GroovyCode(); final TerminalNode groovyCode = ctx.GroovyCode();
if (groovyCode != null) { if (groovyCode != null) {
return this.nodeFactory.dollarScriptletNode( return this.nodeFactory.equalsScriptletNode(
this.getTokenRange(ctx), this.getTokenRange(ctx),
ctx.GroovyCode().getSymbol().getTokenIndex() ctx.GroovyCode().getSymbol().getTokenIndex()
); );
@ -384,12 +385,12 @@ public class DefaultAstBuilderVisitor extends WebViewComponentsParserBaseVisitor
@Override @Override
public Node visitTerminal(TerminalNode node) { public Node visitTerminal(TerminalNode node) {
throw new UnsupportedOperationException(); throw new WebViewComponentBugError("Should not be visiting terminal nodes.");
} }
@Override @Override
public Node visitErrorNode(ErrorNode node) { public Node visitErrorNode(ErrorNode node) {
throw new IllegalStateException("Found an ErrorNode: " + node); throw new WebViewComponentBugError("Should not have found an ErrorNode by this point: " + node);
} }
} }

View File

@ -22,8 +22,10 @@ public class DefaultNodeFactory implements NodeFactory {
CompilationUnitNode.class, CompilationUnitNode.class,
PreambleNode.class, PreambleNode.class,
BodyNode.class, BodyNode.class,
GStringBodyTextNode.class, BodyTextNode.class,
JStringBodyTextNode.class, QuestionNode.class,
HtmlCommentNode.class,
TextNode.class,
TypedComponentNode.class, TypedComponentNode.class,
FragmentComponentNode.class, FragmentComponentNode.class,
ComponentArgsNode.class, ComponentArgsNode.class,
@ -102,13 +104,23 @@ public class DefaultNodeFactory implements NodeFactory {
} }
@Override @Override
public GStringBodyTextNode gStringBodyTextNode(TokenRange tokenRange, List<? extends Node> children) { public BodyTextNode bodyTextNode(TokenRange tokenRange, List<? extends BodyTextChild> children) {
return this.objectFactory.get(GStringBodyTextNode.class, tokenRange, children); return this.objectFactory.get(BodyTextNode.class, tokenRange, children);
} }
@Override @Override
public JStringBodyTextNode jStringBodyTextNode(TokenRange tokenRange, String content) { public QuestionNode questionTagNode(TokenRange tokenRange, List<? extends QuestionTagChild> children) {
return this.objectFactory.get(JStringBodyTextNode.class, tokenRange, content); return this.objectFactory.get(QuestionNode.class, tokenRange, children);
}
@Override
public HtmlCommentNode htmlCommentNode(TokenRange tokenRange, List<? extends HtmlCommentChild> children) {
return this.objectFactory.get(HtmlCommentNode.class, tokenRange, children);
}
@Override
public TextNode textNode(TokenRange tokenRange, String content) {
return this.objectFactory.get(TextNode.class, tokenRange, content);
} }
@Override @Override
@ -196,6 +208,11 @@ public class DefaultNodeFactory implements NodeFactory {
return this.objectFactory.get(ComponentValueNode.class, tokenRange, componentNode); return this.objectFactory.get(ComponentValueNode.class, tokenRange, componentNode);
} }
@Override
public EqualsScriptletNode equalsScriptletNode(TokenRange tokenRange, int groovyIndex) {
return this.objectFactory.get(EqualsScriptletNode.class, tokenRange, groovyIndex);
}
@Override @Override
public PlainScriptletNode plainScriptletNode(TokenRange tokenRange, int groovyIndex) { public PlainScriptletNode plainScriptletNode(TokenRange tokenRange, int groovyIndex) {
return this.objectFactory.get(PlainScriptletNode.class, tokenRange, groovyIndex); return this.objectFactory.get(PlainScriptletNode.class, tokenRange, groovyIndex);

View File

@ -18,9 +18,13 @@ public interface NodeFactory {
BodyNode bodyNode(TokenRange tokenRange, List<? extends BodyChildNode> children); BodyNode bodyNode(TokenRange tokenRange, List<? extends BodyChildNode> children);
GStringBodyTextNode gStringBodyTextNode(TokenRange tokenRange, List<? extends Node> children); BodyTextNode bodyTextNode(TokenRange tokenRange, List<? extends BodyTextChild> children);
JStringBodyTextNode jStringBodyTextNode(TokenRange tokenRange, String content); QuestionNode questionTagNode(TokenRange tokenRange, List<? extends QuestionTagChild> children);
HtmlCommentNode htmlCommentNode(TokenRange tokenRange, List<? extends HtmlCommentChild> children);
TextNode textNode(TokenRange tokenRange, String content);
TypedComponentNode typedComponentNode( TypedComponentNode typedComponentNode(
TokenRange tokenRange, TokenRange tokenRange,
@ -59,6 +63,8 @@ public interface NodeFactory {
ComponentValueNode componentValueNode(TokenRange tokenRange, ComponentNode componentNode); ComponentValueNode componentValueNode(TokenRange tokenRange, ComponentNode componentNode);
EqualsScriptletNode equalsScriptletNode(TokenRange tokenRange, int groovyIndex);
PlainScriptletNode plainScriptletNode(TokenRange tokenRange, int groovyIndex); PlainScriptletNode plainScriptletNode(TokenRange tokenRange, int groovyIndex);
DollarScriptletNode dollarScriptletNode(TokenRange tokenRange, int groovyIndex); DollarScriptletNode dollarScriptletNode(TokenRange tokenRange, int groovyIndex);

View File

@ -21,7 +21,11 @@ public class BodyNode extends AbstractTreeNode {
} }
@Inject @Inject
public BodyNode(NodeExtensionContainer extensionContainer, @Given TokenRange tokenRange, @Given List<? extends BodyChildNode> children) { public BodyNode(
NodeExtensionContainer extensionContainer,
@Given TokenRange tokenRange,
@Given List<? extends BodyChildNode> children
) {
super(tokenRange, extensionContainer, childrenAsNodes(checkChildren(children))); super(tokenRange, extensionContainer, childrenAsNodes(checkChildren(children)));
} }

View File

@ -0,0 +1,9 @@
package groowt.view.component.web.ast.node;
public interface BodyTextChild {
default Node asNode() {
return (Node) this;
}
}

View File

@ -0,0 +1,32 @@
package groowt.view.component.web.ast.node;
import groowt.util.di.annotation.Given;
import groowt.view.component.web.ast.extension.NodeExtensionContainer;
import groowt.view.component.web.util.TokenRange;
import jakarta.inject.Inject;
import java.util.List;
public class BodyTextNode extends AbstractTreeNode implements BodyChildNode {
protected static List<? extends BodyTextChild> checkChildren(List<? extends BodyTextChild> children) {
if (children.isEmpty()) {
throw new IllegalArgumentException("A valid BodyTextNode must have at least one child BodyTextChildNode.");
}
return children;
}
protected static List<Node> childrenAsNodes(List<? extends BodyTextChild> children) {
return children.stream().map(BodyTextChild::asNode).toList();
}
@Inject
public BodyTextNode(
NodeExtensionContainer extensionContainer,
@Given TokenRange tokenRange,
@Given List<? extends BodyTextChild> children
) {
super(tokenRange, extensionContainer, childrenAsNodes(checkChildren(children)));
}
}

View File

@ -6,9 +6,8 @@ import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
public sealed abstract class ComponentNode extends AbstractTreeNode implements BodyChildNode permits public sealed abstract class ComponentNode extends AbstractTreeNode implements BodyChildNode
FragmentComponentNode, permits FragmentComponentNode, TypedComponentNode {
TypedComponentNode {
private final BodyNode bodyNode; private final BodyNode bodyNode;

View File

@ -7,7 +7,7 @@ import groowt.view.component.web.ast.extension.NodeExtensionContainer;
import groowt.view.component.web.util.TokenRange; import groowt.view.component.web.util.TokenRange;
import jakarta.inject.Inject; import jakarta.inject.Inject;
public class DollarReferenceNode extends AbstractLeafNode { public class DollarReferenceNode extends AbstractLeafNode implements GroovyBodyNode {
private final int groovyTokenIndex; private final int groovyTokenIndex;

View File

@ -7,7 +7,7 @@ import groowt.view.component.web.ast.extension.NodeExtensionContainer;
import groowt.view.component.web.util.TokenRange; import groowt.view.component.web.util.TokenRange;
import jakarta.inject.Inject; import jakarta.inject.Inject;
public class DollarScriptletNode extends AbstractLeafNode { public class DollarScriptletNode extends AbstractLeafNode implements GroovyBodyNode {
private final GStringScriptletExtension gStringScriptlet; private final GStringScriptletExtension gStringScriptlet;

View File

@ -0,0 +1,50 @@
package groowt.view.component.web.ast.node;
import groowt.util.di.annotation.Given;
import groowt.view.component.web.antlr.TokenList;
import groowt.view.component.web.ast.extension.GroovyCodeNodeExtension;
import groowt.view.component.web.ast.extension.NodeExtensionContainer;
import groowt.view.component.web.util.TokenRange;
import org.antlr.v4.runtime.Token;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
public class EqualsScriptletNode extends AbstractLeafNode implements GroovyBodyNode {
private final int groovyIndex;
private final GroovyCodeNodeExtension groovyCode;
public EqualsScriptletNode(
TokenList tokenList,
NodeExtensionContainer extensionContainer,
@Given TokenRange tokenRange,
@Given int groovyIndex
) {
super(tokenRange, extensionContainer);
this.groovyIndex = groovyIndex;
this.groovyCode = this.createGroovyCode(tokenList);
}
protected GroovyCodeNodeExtension createGroovyCode(TokenList tokenList) {
return this.createExtension(
GroovyCodeNodeExtension.class,
TokenRange.fromIndex(tokenList, this.groovyIndex),
(Function<? super List<Token>, String>) this::toValidGroovyCode
);
}
protected String toValidGroovyCode(List<Token> groovyTokens) {
return "{ -> " + groovyTokens.stream().map(Token::getText).collect(Collectors.joining()) + " }";
}
public int getGroovyIndex() {
return this.groovyIndex;
}
public GroovyCodeNodeExtension getGroovyCode() {
return this.groovyCode;
}
}

View File

@ -8,6 +8,7 @@ import jakarta.inject.Inject;
import java.util.List; import java.util.List;
@Deprecated
public class GStringBodyTextNode extends AbstractTreeNode implements BodyChildNode { public class GStringBodyTextNode extends AbstractTreeNode implements BodyChildNode {
protected static List<? extends Node> checkChildren(List<? extends Node> children) { protected static List<? extends Node> checkChildren(List<? extends Node> children) {

View File

@ -0,0 +1,10 @@
package groowt.view.component.web.ast.node;
public interface GroovyBodyNode extends BodyTextChild, HtmlCommentChild, QuestionTagChild {
@Override
default Node asNode() {
return (Node) this;
}
}

View File

@ -0,0 +1,9 @@
package groowt.view.component.web.ast.node;
public interface HtmlCommentChild {
default Node asNode() {
return (Node) this;
}
}

View File

@ -0,0 +1,21 @@
package groowt.view.component.web.ast.node;
import groowt.util.di.annotation.Given;
import groowt.view.component.web.ast.extension.NodeExtensionContainer;
import groowt.view.component.web.util.TokenRange;
import jakarta.inject.Inject;
import java.util.List;
public class HtmlCommentNode extends AbstractTreeNode implements BodyTextChild {
@Inject
public HtmlCommentNode(
NodeExtensionContainer extensionContainer,
@Given TokenRange tokenRange,
@Given List<? extends HtmlCommentChild> children
) {
super(tokenRange, extensionContainer, children.stream().map(HtmlCommentChild::asNode).toList());
}
}

View File

@ -5,6 +5,7 @@ import groowt.view.component.web.ast.extension.NodeExtensionContainer;
import groowt.view.component.web.util.TokenRange; import groowt.view.component.web.util.TokenRange;
import jakarta.inject.Inject; import jakarta.inject.Inject;
@Deprecated
public class JStringBodyTextNode extends AbstractLeafNode implements BodyChildNode { public class JStringBodyTextNode extends AbstractLeafNode implements BodyChildNode {
private final String content; private final String content;

View File

@ -12,7 +12,7 @@ import java.util.List;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class PlainScriptletNode extends AbstractLeafNode implements BodyChildNode { public class PlainScriptletNode extends AbstractLeafNode implements GroovyBodyNode {
private final int groovyIndex; private final int groovyIndex;
private final GroovyCodeNodeExtension groovyCode; private final GroovyCodeNodeExtension groovyCode;
@ -38,7 +38,7 @@ public class PlainScriptletNode extends AbstractLeafNode implements BodyChildNod
} }
protected String toValidGroovyCode(List<Token> groovyTokens) { protected String toValidGroovyCode(List<Token> groovyTokens) {
return "{ Writer out ->\n" + groovyTokens.stream().map(Token::getText).collect(Collectors.joining()) + "\n}"; return "{ Writer out -> " + groovyTokens.stream().map(Token::getText).collect(Collectors.joining()) + " }";
} }
public int getGroovyIndex() { public int getGroovyIndex() {

View File

@ -0,0 +1,21 @@
package groowt.view.component.web.ast.node;
import groowt.util.di.annotation.Given;
import groowt.view.component.web.ast.extension.NodeExtensionContainer;
import groowt.view.component.web.util.TokenRange;
import jakarta.inject.Inject;
import java.util.List;
public class QuestionNode extends AbstractTreeNode implements BodyTextChild {
@Inject
public QuestionNode(
NodeExtensionContainer extensionContainer,
@Given TokenRange tokenRange,
@Given List<? extends QuestionTagChild> children
) {
super(tokenRange, extensionContainer, children.stream().map(QuestionTagChild::asNode).toList());
}
}

View File

@ -0,0 +1,9 @@
package groowt.view.component.web.ast.node;
public interface QuestionTagChild {
default Node asNode() {
return (Node) this;
}
}

View File

@ -0,0 +1,31 @@
package groowt.view.component.web.ast.node;
import groowt.util.di.annotation.Given;
import groowt.view.component.web.ast.extension.NodeExtensionContainer;
import groowt.view.component.web.util.TokenRange;
import jakarta.inject.Inject;
public class TextNode extends AbstractLeafNode implements BodyTextChild, HtmlCommentChild, QuestionTagChild {
private final String content;
@Inject
public TextNode(
NodeExtensionContainer extensionContainer,
@Given TokenRange tokenRange,
@Given String content
) {
super(tokenRange, extensionContainer);
this.content = content;
}
@Override
public Node asNode() {
return this;
}
public String getContent() {
return this.content;
}
}

View File

@ -20,30 +20,34 @@ public record SourcePosition(int line, int column) {
public static SourcePosition fromEndOfToken(Token token) { public static SourcePosition fromEndOfToken(Token token) {
final var text = token.getText(); final var text = token.getText();
int line = token.getLine(); if (token.getType() == Token.EOF || text.length() == 1) {
int col = token.getCharPositionInLine() + 1; return new SourcePosition(token.getLine(), token.getCharPositionInLine() + 1);
int i = 0; } else {
while (i < text.length()) { int line = token.getLine();
final char c0 = text.charAt(i); int col = token.getCharPositionInLine() + 1;
if (c0 == '\r') { int i = 0;
line++; while (i < text.length()) {
col = 1; final char c0 = text.charAt(i);
final char c1 = text.charAt(i + 1); if (c0 == '\r') {
if (c1 == '\n') { line++;
i += 2; col = 1;
final char c1 = text.charAt(i + 1);
if (c1 == '\n') {
i += 2;
} else {
i++;
}
} else if (c0 == '\n') {
line++;
col = 1;
i++;
} else { } else {
col++;
i++; i++;
} }
} else if (c0 == '\n') {
line++;
col = 1;
i++;
} else {
col++;
i++;
} }
return new SourcePosition(line, col);
} }
return new SourcePosition(line, col);
} }
public String toStringShort() { public String toStringShort() {

View File

@ -1,69 +0,0 @@
CompilationUnitNode(1,1..21,6)
PreambleNode(1,1..6,1)
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,1](<!DOCTYPE html>\n)
TypedComponentNode(7,1..20,8)
ComponentArgsNode(7,2..7,6)
StringComponentTypeNode(7,2..7,6)
StringIdentifier[7,2](html)
BodyNode(7,7..20,1)
TypedComponentNode(8,5..8,18)
ComponentArgsNode(8,6..8,10)
StringComponentTypeNode(8,6..8,10)
StringIdentifier[8,6](head)
TypedComponentNode(9,5..19,12)
ComponentArgsNode(9,6..9,10)
StringComponentTypeNode(9,6..9,10)
StringIdentifier[9,6](body)
BodyNode(9,11..19,5)
TypedComponentNode(10,9..10,29)
ComponentArgsNode(10,10..10,12)
StringComponentTypeNode(10,10..10,12)
StringIdentifier[10,10](h1)
BodyNode(10,13..10,24)
GStringBodyTextNode(10,13..10,24)
DollarScriptletNode(10,13..10,24)
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)
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)
TypedIdentifier[12,14](Case)
KeyValueAttrNode(12,19..12,36)
KeyNode(12,19..12,24)
AttributeIdentifier[12,19](cond)
Equals[12,23](=)
ClosureValueNode(12,24..12,36)
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)
StringIdentifier[13,18](p)
BodyNode(13,20..13,33)
JStringBodyTextNode(13,20..13,33)
RawText[13,20](It's true! :))
TypedComponentNode(15,13..17,23)
ComponentArgsNode(15,14..15,21)
ClassComponentTypeNode(15,14..15,21)
TypedIdentifier[15,14](Default)
BodyNode(15,22..17,13)
TypedComponentNode(16,17..16,40)
ComponentArgsNode(16,18..16,19)
StringComponentTypeNode(16,18..16,19)
StringIdentifier[16,18](p)
BodyNode(16,20..16,36)
JStringBodyTextNode(16,20..16,36)
RawText[16,20](It's false... :()

View File

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

View File

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

View File

@ -10,6 +10,7 @@ import org.junit.jupiter.api.Test
import static groowt.view.component.web.antlr.TokenUtil.getTokenName import static groowt.view.component.web.antlr.TokenUtil.getTokenName
import static groowt.view.component.web.antlr.WebViewComponentsLexer.GroovyCode import static groowt.view.component.web.antlr.WebViewComponentsLexer.GroovyCode
import static groowt.view.component.web.antlr.WebViewComponentsLexer.PreambleBreak import static groowt.view.component.web.antlr.WebViewComponentsLexer.PreambleBreak
import static groowt.view.component.web.antlr.WebViewComponentsLexerBase.RawText
import static org.antlr.v4.runtime.Recognizer.EOF import static org.antlr.v4.runtime.Recognizer.EOF
import static org.junit.jupiter.api.Assertions.* import static org.junit.jupiter.api.Assertions.*
@ -50,11 +51,11 @@ class WebViewComponentsTokenStreamTests {
def lexer = new WebViewComponentsLexer(input) def lexer = new WebViewComponentsLexer(input)
def tokenStream = new WebViewComponentsTokenStream(lexer) def tokenStream = new WebViewComponentsTokenStream(lexer)
def tokens = tokenStream.allTokens def tokens = tokenStream.allTokens
assertTypes([PreambleBreak, GroovyCode, PreambleBreak, EOF], tokens) assertTypes([PreambleBreak, GroovyCode, PreambleBreak, RawText, EOF], tokens)
assertMergedGroovyCodeToken(tokens[1]) { assertMergedGroovyCodeToken(tokens[1]) {
assertEquals('println \'Hello, World!\' // comment\n', it.text) assertEquals('println \'Hello, World!\' // comment\n', it.text)
} }
assertIterableEquals(0..3, tokens*.tokenIndex) assertIterableEquals(0..4, tokens*.tokenIndex)
} }
} }

View File

@ -7,57 +7,15 @@ import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.Token;
import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestFactory;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import static groowt.view.component.web.antlr.WebViewComponentsLexer.*;
import static org.junit.jupiter.api.Assertions.*;
public class WebViewComponentsLexerTests { public class WebViewComponentsLexerTests {
private static void assertTokenType(int type, Token token) {
assertEquals(
type,
token.getType(),
() -> "Expected " + VOCABULARY.getDisplayName(type)
+ " but got " + VOCABULARY.getDisplayName(token.getType())
);
}
@Test
public void helloTarget() {
final var input = CharStreams.fromString("Hello, $target!");
final var lexer = new WebViewComponentsLexer(input);
final var tokenStream = new WebViewComponentsTokenStream(lexer);
final var allTokens = tokenStream.getAllTokens();
assertEquals(5, allTokens.size(), () -> {
return "Wrong number of tokens; tokens: " + allTokens.stream()
.map(Token::toString)
.collect(Collectors.joining(", "));
});
final var t0 = allTokens.get(0);
final var t1 = allTokens.get(1);
final var t2 = allTokens.get(2);
final var t3 = allTokens.get(3);
final var t4 = allTokens.get(4);
assertEquals("Hello, ", t0.getText());
assertTokenType(RawText, t0);
assertEquals("$", t1.getText());
assertTokenType(DollarReferenceStart, t1);
assertEquals("target", t2.getText());
assertTokenType(GroovyCode, t2);
assertInstanceOf(MergedGroovyCodeToken.class, t2);
assertEquals("!", t3.getText());
assertTokenType(RawText, t3);
assertTokenType(EOF, t4);
}
@TestFactory @TestFactory
public Collection<DynamicTest> lexerFileTests() { public Collection<DynamicTest> lexerFileTests() {
return FileComparisonTestUtil.getTestsFor( return FileComparisonTestUtil.getTestsFor(
@ -71,10 +29,8 @@ public class WebViewComponentsLexerTests {
sourceFile -> { sourceFile -> {
final CharStream input = CharStreams.fromString(FileUtil.readFile(sourceFile)); final CharStream input = CharStreams.fromString(FileUtil.readFile(sourceFile));
final WebViewComponentsLexer lexer = new WebViewComponentsLexer(input); final WebViewComponentsLexer lexer = new WebViewComponentsLexer(input);
final WebViewComponentsTokenStream tokenStream = new WebViewComponentsTokenStream( // include all (!) (non-skipped) tokens for testing via Set.of()
lexer, final WebViewComponentsTokenStream tokenStream = new WebViewComponentsTokenStream(lexer, Set.of());
Set.of(WebViewComponentsLexer.HIDDEN) // include bad tokens for testing
);
final List<Token> allTokens = tokenStream.getAllTokensSkipEOF(); final List<Token> allTokens = tokenStream.getAllTokensSkipEOF();
final var sb = new StringBuilder(); final var sb = new StringBuilder();
for (int i = 0; i < allTokens.size(); i++) { for (int i = 0; i < allTokens.size(); i++) {

View File

@ -27,7 +27,7 @@ public final class WebViewComponentsParserTests {
private static final Logger logger = LoggerFactory.getLogger(WebViewComponentsParserTests.class); 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 parserFileBase = String.join(File.separator, "src", "test", "parser");
private static final String parserTreeFileBase = String.join(File.separator, parserFileBase, "trees"); private static final String parserTreeFileBase = String.join(File.separator, parserFileBase, "parse-tree-files");
private static final String parseTreeFileSuffix = "_parseTree"; private static final String parseTreeFileSuffix = "_parseTree";
private static final String parseTreeFileExtension = ".txt"; private static final String parseTreeFileExtension = ".txt";
private static final Set<String> parserFileGlobs = Set.of( private static final Set<String> parserFileGlobs = Set.of(

View File

@ -0,0 +1 @@
<!DOCTYPE html>

View File

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

View File

@ -0,0 +1,3 @@
<Test
someAttr
/>

View File

@ -0,0 +1 @@
Hello!

View File

@ -0,0 +1,2 @@
---
----

View File

@ -0,0 +1,2 @@
----
---

View File

@ -0,0 +1,2 @@
---
------

View File

@ -0,0 +1,3 @@
---
def regex = $/match --- me $ $test ${test('---')} $$ $/ / /$
---

View File

@ -0,0 +1,3 @@
---
def test = "---$test${test('---')}"
---

View File

@ -0,0 +1,3 @@
---
def test = '---'
---

View File

@ -0,0 +1,3 @@
---
def regex = (/match --- \/ me/)
---

View File

@ -0,0 +1,5 @@
---
def test = """
--- $test ${test("---")} " \$
"""
---

View File

@ -0,0 +1,5 @@
---
def test = '''
---
'''
---

View File

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

View File

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

View File

@ -1,7 +1,7 @@
0: PreambleBreak[1,1](---\n) 0: PreambleBreak[1,1](---\n)
1: GroovyCode[2,1](import some.Thing // a comment...World!') 1: GroovyCode[2,1](import some.Thing // a comment...rld!'\n)
2: PreambleBreak[4,31](\n---\n) 2: PreambleBreak[5,1](---)
3: RawText[6,1](<!DOCTYPE html>\n) 3: RawText[5,4](\n<!DOCTYPE html>\n)
4: ComponentOpen[7,1](<) 4: ComponentOpen[7,1](<)
5: StringIdentifier[7,2](html) 5: StringIdentifier[7,2](html)
6: ComponentClose[7,6](>) 6: ComponentClose[7,6](>)
@ -33,50 +33,51 @@
32: RawText[11,33](\n ) 32: RawText[11,33](\n )
33: ComponentOpen[12,13](<) 33: ComponentOpen[12,13](<)
34: TypedIdentifier[12,14](Case) 34: TypedIdentifier[12,14](Case)
35: AttributeIdentifier[12,19](cond) 35: Nlws[12,18]( )
36: Equals[12,23](=) 36: AttributeIdentifier[12,19](cond)
37: ClosureAttrValueStart[12,24]({) 37: Equals[12,23](=)
38: GroovyCode[12,25](isItTrue()) 38: ClosureAttrValueStart[12,24]({)
39: ClosureAttrValueEnd[12,35](}) 39: GroovyCode[12,25](isItTrue())
40: ComponentClose[12,36](>) 40: ClosureAttrValueEnd[12,35](})
41: RawText[12,37](\n ) 41: ComponentClose[12,36](>)
42: ComponentOpen[13,17](<) 42: RawText[12,37](\n )
43: StringIdentifier[13,18](p) 43: ComponentOpen[13,17](<)
44: ComponentClose[13,19](>) 44: StringIdentifier[13,18](p)
45: RawText[13,20](It's true! :)) 45: ComponentClose[13,19](>)
46: ClosingComponentOpen[13,33](</) 46: RawText[13,20](It's true! :))
47: StringIdentifier[13,35](p) 47: ClosingComponentOpen[13,33](</)
48: ComponentClose[13,36](>) 48: StringIdentifier[13,35](p)
49: RawText[13,37](\n ) 49: ComponentClose[13,36](>)
50: ClosingComponentOpen[14,13](</) 50: RawText[13,37](\n )
51: TypedIdentifier[14,15](Case) 51: ClosingComponentOpen[14,13](</)
52: ComponentClose[14,19](>) 52: TypedIdentifier[14,15](Case)
53: RawText[14,20](\n ) 53: ComponentClose[14,19](>)
54: ComponentOpen[15,13](<) 54: RawText[14,20](\n )
55: TypedIdentifier[15,14](Default) 55: ComponentOpen[15,13](<)
56: ComponentClose[15,21](>) 56: TypedIdentifier[15,14](Default)
57: RawText[15,22](\n ) 57: ComponentClose[15,21](>)
58: ComponentOpen[16,17](<) 58: RawText[15,22](\n )
59: StringIdentifier[16,18](p) 59: ComponentOpen[16,17](<)
60: ComponentClose[16,19](>) 60: StringIdentifier[16,18](p)
61: RawText[16,20](It's false... :() 61: ComponentClose[16,19](>)
62: ClosingComponentOpen[16,36](</) 62: RawText[16,20](It's false... :()
63: StringIdentifier[16,38](p) 63: ClosingComponentOpen[16,36](</)
64: ComponentClose[16,39](>) 64: StringIdentifier[16,38](p)
65: RawText[16,40](\n ) 65: ComponentClose[16,39](>)
66: ClosingComponentOpen[17,13](</) 66: RawText[16,40](\n )
67: TypedIdentifier[17,15](Default) 67: ClosingComponentOpen[17,13](</)
68: ComponentClose[17,22](>) 68: TypedIdentifier[17,15](Default)
69: RawText[17,23](\n ) 69: ComponentClose[17,22](>)
70: ClosingComponentOpen[18,9](</) 70: RawText[17,23](\n )
71: TypedIdentifier[18,11](groowt.view.web.Select) 71: ClosingComponentOpen[18,9](</)
72: ComponentClose[18,33](>) 72: TypedIdentifier[18,11](groowt.view.web.Select)
73: RawText[18,34](\n ) 73: ComponentClose[18,33](>)
74: ClosingComponentOpen[19,5](</) 74: RawText[18,34](\n )
75: StringIdentifier[19,7](body) 75: ClosingComponentOpen[19,5](</)
76: ComponentClose[19,11](>) 76: StringIdentifier[19,7](body)
77: RawText[19,12](\n) 77: ComponentClose[19,11](>)
78: ClosingComponentOpen[20,1](</) 78: RawText[19,12](\n)
79: StringIdentifier[20,3](html) 79: ClosingComponentOpen[20,1](</)
80: ComponentClose[20,7](>) 80: StringIdentifier[20,3](html)
81: RawText[20,8](\n) 81: ComponentClose[20,7](>)
82: RawText[20,8](\n)

View File

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

View File

@ -0,0 +1 @@
0: RawText[1,1](<!DOCTYPE html>\n)

View File

@ -0,0 +1,3 @@
0: FragmentOpen[1,1](<>)
1: FragmentClose[1,3](</>)
2: RawText[1,6](\n)

View File

@ -0,0 +1,7 @@
0: ComponentOpen[1,1](<)
1: TypedIdentifier[1,2](Test)
2: Nlws[1,6](\n )
3: AttributeIdentifier[2,5](someAttr)
4: Nlws[2,13](\n)
5: ComponentSelfClose[3,1](/>)
6: RawText[3,3](\n)

View File

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

View File

@ -0,0 +1,3 @@
0: PreambleBreak[1,1](---\n)
1: PreambleBreak[2,1](---)
2: RawText[2,4](-\n)

View File

@ -0,0 +1,4 @@
0: PreambleBreak[1,1](---)
1: GroovyCode[1,4](-\n)
2: PreambleBreak[2,1](---)
3: RawText[2,4](\n)

View File

@ -0,0 +1,3 @@
0: PreambleBreak[1,1](---\n)
1: PreambleBreak[2,1](---)
2: RawText[2,4](---\n)

View File

@ -0,0 +1,4 @@
0: PreambleBreak[1,1](---\n)
1: GroovyCode[2,1](// ---\n)
2: PreambleBreak[3,1](---)
3: RawText[3,4](\n)

View File

@ -0,0 +1,4 @@
0: PreambleBreak[1,1](---\n)
1: GroovyCode[2,1](def regex = $/match --- me $ $... / /$\n)
2: PreambleBreak[3,1](---)
3: RawText[3,4](\n)

View File

@ -0,0 +1,4 @@
0: PreambleBreak[1,1](---\n)
1: GroovyCode[2,1](def test = "---$test${test('---')}"\n)
2: PreambleBreak[3,1](---)
3: RawText[3,4](\n)

View File

@ -0,0 +1,4 @@
0: PreambleBreak[1,1](---\n)
1: GroovyCode[2,1](def test = '---'\n)
2: PreambleBreak[3,1](---)
3: RawText[3,4](\n)

View File

@ -0,0 +1,4 @@
0: PreambleBreak[1,1](---\n)
1: GroovyCode[2,1](def regex = (/match --- \/ me/)\n)
2: PreambleBreak[3,1](---)
3: RawText[3,4](\n)

View File

@ -0,0 +1,4 @@
0: PreambleBreak[1,1](---\n)
1: GroovyCode[2,1](def test = """\n--- $test ${te...\n"""\n)
2: PreambleBreak[5,1](---)
3: RawText[5,4](\n)

View File

@ -0,0 +1,4 @@
0: PreambleBreak[1,1](---\n)
1: GroovyCode[2,1](def test = '''\n---\n'''\n)
2: PreambleBreak[5,1](---)
3: RawText[5,4](\n)

View File

@ -0,0 +1,5 @@
0: ComponentOpen[1,1](<)
1: TypedIdentifier[1,2](Test)
2: Nlws[1,6]( )
3: ComponentSelfClose[1,7](/>)
4: RawText[1,9](\n)

View File

@ -1 +1,2 @@
0: ClosingComponentOpen[1,1](</) 0: ClosingComponentOpen[1,1](</)
1: Nlws[1,3](\n)

View File

@ -0,0 +1 @@
0: RawText[1,1]( ---\n)

View File

@ -0,0 +1,4 @@
0: PreambleBreak[1,1](---\n)
1: GroovyCode[2,1](package test\n)
2: PreambleBreak[3,1](---)
3: RawText[3,4](\n)

View File

@ -0,0 +1,3 @@
---
package test
---

View File

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

View File

@ -0,0 +1 @@
Hello, World!

View File

@ -1,94 +1,93 @@
compilationUnit[1,1..21,1] compilationUnit[1,1..21,1]
preamble[1,1..4,31] preamble[1,1..5,4]
PreambleBreak[1,1](---\n) PreambleBreak[1,1](---\n)
GroovyCode[2,1](import some.Thing // a comment\n\ndef greeting = 'Hello, World!') GroovyCode[2,1](import some.Thing // a comment\n\ndef greeting = 'Hello, World!'\n)
PreambleBreak[4,31](\n---\n) PreambleBreak[5,1](---)
body[6,1..20,8] body[5,4..20,8]
bodyText[6,1..6,1] bodyText[5,4..7,1]
jStringBodyText[6,1..6,1] text[5,4..7,1]
RawText[6,1](<!DOCTYPE html>\n) RawText[5,4](\n<!DOCTYPE html>\n)
component[7,1..20,7] component[7,1..20,7]
componentWithChildren[7,1..20,7] componentWithChildren[7,1..20,7]
openComponent[7,1..7,6] openComponent[7,1..7,6]
ComponentOpen[7,1](<) ComponentOpen[7,1](<)
componentArgs[7,2..7,2] componentArgs[7,2..7,6]
componentType[7,2..7,2] componentType[7,2..7,6]
StringIdentifier[7,2](html) StringIdentifier[7,2](html)
ComponentClose[7,6](>) ComponentClose[7,6](>)
body[7,7..19,12] body[7,7..19,12]
bodyText[7,7..7,7] bodyText[7,7..8,5]
jStringBodyText[7,7..7,7] text[7,7..8,5]
RawText[7,7](\n ) RawText[7,7](\n )
component[8,5..8,17] component[8,5..8,17]
componentWithChildren[8,5..8,17] componentWithChildren[8,5..8,17]
openComponent[8,5..8,10] openComponent[8,5..8,10]
ComponentOpen[8,5](<) ComponentOpen[8,5](<)
componentArgs[8,6..8,6] componentArgs[8,6..8,10]
componentType[8,6..8,6] componentType[8,6..8,10]
StringIdentifier[8,6](head) StringIdentifier[8,6](head)
ComponentClose[8,10](>) ComponentClose[8,10](>)
closingComponent[8,11..8,17] closingComponent[8,11..8,17]
ClosingComponentOpen[8,11](</) ClosingComponentOpen[8,11](</)
componentType[8,13..8,13] componentType[8,13..8,17]
StringIdentifier[8,13](head) StringIdentifier[8,13](head)
ComponentClose[8,17](>) ComponentClose[8,17](>)
bodyText[8,18..8,18] bodyText[8,18..9,5]
jStringBodyText[8,18..8,18] text[8,18..9,5]
RawText[8,18](\n ) RawText[8,18](\n )
component[9,5..19,11] component[9,5..19,11]
componentWithChildren[9,5..19,11] componentWithChildren[9,5..19,11]
openComponent[9,5..9,10] openComponent[9,5..9,10]
ComponentOpen[9,5](<) ComponentOpen[9,5](<)
componentArgs[9,6..9,6] componentArgs[9,6..9,10]
componentType[9,6..9,6] componentType[9,6..9,10]
StringIdentifier[9,6](body) StringIdentifier[9,6](body)
ComponentClose[9,10](>) ComponentClose[9,10](>)
body[9,11..18,34] body[9,11..19,5]
bodyText[9,11..9,11] bodyText[9,11..10,9]
jStringBodyText[9,11..9,11] text[9,11..10,9]
RawText[9,11](\n ) RawText[9,11](\n )
component[10,9..10,28] component[10,9..10,28]
componentWithChildren[10,9..10,28] componentWithChildren[10,9..10,28]
openComponent[10,9..10,12] openComponent[10,9..10,12]
ComponentOpen[10,9](<) ComponentOpen[10,9](<)
componentArgs[10,10..10,10] componentArgs[10,10..10,12]
componentType[10,10..10,10] componentType[10,10..10,12]
StringIdentifier[10,10](h1) StringIdentifier[10,10](h1)
ComponentClose[10,12](>) ComponentClose[10,12](>)
body[10,13..10,23] body[10,13..10,23]
bodyText[10,13..10,23] bodyText[10,13..10,23]
gStringBodyText[10,13..10,23] bodyTextGroovyElement[10,13..10,23]
gStringBodyTextGroovyElement[10,13..10,23] dollarScriptlet[10,13..10,23]
dollarScriptlet[10,13..10,23] DollarScriptletOpen[10,13](${)
DollarScriptletOpen[10,13](${) GroovyCode[10,15](greeting)
GroovyCode[10,15](greeting) DollarScriptletClose[10,23](})
DollarScriptletClose[10,23](})
closingComponent[10,24..10,28] closingComponent[10,24..10,28]
ClosingComponentOpen[10,24](</) ClosingComponentOpen[10,24](</)
componentType[10,26..10,26] componentType[10,26..10,28]
StringIdentifier[10,26](h1) StringIdentifier[10,26](h1)
ComponentClose[10,28](>) ComponentClose[10,28](>)
bodyText[10,29..10,29] bodyText[10,29..11,9]
jStringBodyText[10,29..10,29] text[10,29..11,9]
RawText[10,29](\n ) RawText[10,29](\n )
component[11,9..18,33] component[11,9..18,33]
componentWithChildren[11,9..18,33] componentWithChildren[11,9..18,33]
openComponent[11,9..11,32] openComponent[11,9..11,32]
ComponentOpen[11,9](<) ComponentOpen[11,9](<)
componentArgs[11,10..11,10] componentArgs[11,10..11,32]
componentType[11,10..11,10] componentType[11,10..11,32]
TypedIdentifier[11,10](groowt.view.web.Select) TypedIdentifier[11,10](groowt.view.web.Select)
ComponentClose[11,32](>) ComponentClose[11,32](>)
body[11,33..17,23] body[11,33..18,9]
bodyText[11,33..11,33] bodyText[11,33..12,13]
jStringBodyText[11,33..11,33] text[11,33..12,13]
RawText[11,33](\n ) RawText[11,33](\n )
component[12,13..14,19] component[12,13..14,19]
componentWithChildren[12,13..14,19] componentWithChildren[12,13..14,19]
openComponent[12,13..12,36] openComponent[12,13..12,36]
ComponentOpen[12,13](<) ComponentOpen[12,13](<)
componentArgs[12,14..12,35] componentArgs[12,14..12,35]
componentType[12,14..12,14] componentType[12,14..12,18]
TypedIdentifier[12,14](Case) TypedIdentifier[12,14](Case)
attr[12,19..12,35] attr[12,19..12,35]
keyValueAttr[12,19..12,35] keyValueAttr[12,19..12,35]
@ -100,9 +99,9 @@ compilationUnit[1,1..21,1]
GroovyCode[12,25](isItTrue()) GroovyCode[12,25](isItTrue())
ClosureAttrValueEnd[12,35](}) ClosureAttrValueEnd[12,35](})
ComponentClose[12,36](>) ComponentClose[12,36](>)
body[12,37..13,37] body[12,37..14,13]
bodyText[12,37..12,37] bodyText[12,37..13,17]
jStringBodyText[12,37..12,37] text[12,37..13,17]
RawText[12,37](\n ) RawText[12,37](\n )
component[13,17..13,36] component[13,17..13,36]
componentWithChildren[13,17..13,36] componentWithChildren[13,17..13,36]
@ -112,37 +111,37 @@ compilationUnit[1,1..21,1]
componentType[13,18..13,18] componentType[13,18..13,18]
StringIdentifier[13,18](p) StringIdentifier[13,18](p)
ComponentClose[13,19](>) ComponentClose[13,19](>)
body[13,20..13,20] body[13,20..13,33]
bodyText[13,20..13,20] bodyText[13,20..13,33]
jStringBodyText[13,20..13,20] text[13,20..13,33]
RawText[13,20](It's true! :)) RawText[13,20](It's true! :))
closingComponent[13,33..13,36] closingComponent[13,33..13,36]
ClosingComponentOpen[13,33](</) ClosingComponentOpen[13,33](</)
componentType[13,35..13,35] componentType[13,35..13,35]
StringIdentifier[13,35](p) StringIdentifier[13,35](p)
ComponentClose[13,36](>) ComponentClose[13,36](>)
bodyText[13,37..13,37] bodyText[13,37..14,13]
jStringBodyText[13,37..13,37] text[13,37..14,13]
RawText[13,37](\n ) RawText[13,37](\n )
closingComponent[14,13..14,19] closingComponent[14,13..14,19]
ClosingComponentOpen[14,13](</) ClosingComponentOpen[14,13](</)
componentType[14,15..14,15] componentType[14,15..14,19]
TypedIdentifier[14,15](Case) TypedIdentifier[14,15](Case)
ComponentClose[14,19](>) ComponentClose[14,19](>)
bodyText[14,20..14,20] bodyText[14,20..15,13]
jStringBodyText[14,20..14,20] text[14,20..15,13]
RawText[14,20](\n ) RawText[14,20](\n )
component[15,13..17,22] component[15,13..17,22]
componentWithChildren[15,13..17,22] componentWithChildren[15,13..17,22]
openComponent[15,13..15,21] openComponent[15,13..15,21]
ComponentOpen[15,13](<) ComponentOpen[15,13](<)
componentArgs[15,14..15,14] componentArgs[15,14..15,21]
componentType[15,14..15,14] componentType[15,14..15,21]
TypedIdentifier[15,14](Default) TypedIdentifier[15,14](Default)
ComponentClose[15,21](>) ComponentClose[15,21](>)
body[15,22..16,40] body[15,22..17,13]
bodyText[15,22..15,22] bodyText[15,22..16,17]
jStringBodyText[15,22..15,22] text[15,22..16,17]
RawText[15,22](\n ) RawText[15,22](\n )
component[16,17..16,39] component[16,17..16,39]
componentWithChildren[16,17..16,39] componentWithChildren[16,17..16,39]
@ -152,48 +151,48 @@ compilationUnit[1,1..21,1]
componentType[16,18..16,18] componentType[16,18..16,18]
StringIdentifier[16,18](p) StringIdentifier[16,18](p)
ComponentClose[16,19](>) ComponentClose[16,19](>)
body[16,20..16,20] body[16,20..16,36]
bodyText[16,20..16,20] bodyText[16,20..16,36]
jStringBodyText[16,20..16,20] text[16,20..16,36]
RawText[16,20](It's false... :() RawText[16,20](It's false... :()
closingComponent[16,36..16,39] closingComponent[16,36..16,39]
ClosingComponentOpen[16,36](</) ClosingComponentOpen[16,36](</)
componentType[16,38..16,38] componentType[16,38..16,38]
StringIdentifier[16,38](p) StringIdentifier[16,38](p)
ComponentClose[16,39](>) ComponentClose[16,39](>)
bodyText[16,40..16,40] bodyText[16,40..17,13]
jStringBodyText[16,40..16,40] text[16,40..17,13]
RawText[16,40](\n ) RawText[16,40](\n )
closingComponent[17,13..17,22] closingComponent[17,13..17,22]
ClosingComponentOpen[17,13](</) ClosingComponentOpen[17,13](</)
componentType[17,15..17,15] componentType[17,15..17,22]
TypedIdentifier[17,15](Default) TypedIdentifier[17,15](Default)
ComponentClose[17,22](>) ComponentClose[17,22](>)
bodyText[17,23..17,23] bodyText[17,23..18,9]
jStringBodyText[17,23..17,23] text[17,23..18,9]
RawText[17,23](\n ) RawText[17,23](\n )
closingComponent[18,9..18,33] closingComponent[18,9..18,33]
ClosingComponentOpen[18,9](</) ClosingComponentOpen[18,9](</)
componentType[18,11..18,11] componentType[18,11..18,33]
TypedIdentifier[18,11](groowt.view.web.Select) TypedIdentifier[18,11](groowt.view.web.Select)
ComponentClose[18,33](>) ComponentClose[18,33](>)
bodyText[18,34..18,34] bodyText[18,34..19,5]
jStringBodyText[18,34..18,34] text[18,34..19,5]
RawText[18,34](\n ) RawText[18,34](\n )
closingComponent[19,5..19,11] closingComponent[19,5..19,11]
ClosingComponentOpen[19,5](</) ClosingComponentOpen[19,5](</)
componentType[19,7..19,7] componentType[19,7..19,11]
StringIdentifier[19,7](body) StringIdentifier[19,7](body)
ComponentClose[19,11](>) ComponentClose[19,11](>)
bodyText[19,12..19,12] bodyText[19,12..19,12]
jStringBodyText[19,12..19,12] text[19,12..19,12]
RawText[19,12](\n) RawText[19,12](\n)
closingComponent[20,1..20,7] closingComponent[20,1..20,7]
ClosingComponentOpen[20,1](</) ClosingComponentOpen[20,1](</)
componentType[20,3..20,3] componentType[20,3..20,7]
StringIdentifier[20,3](html) StringIdentifier[20,3](html)
ComponentClose[20,7](>) ComponentClose[20,7](>)
bodyText[20,8..20,8] bodyText[20,8..20,8]
jStringBodyText[20,8..20,8] text[20,8..20,8]
RawText[20,8](\n) RawText[20,8](\n)
EOF[21,1](<EOF>) EOF[21,1](<EOF>)

View File

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

View File

@ -0,0 +1,14 @@
compilationUnit[1,1..2,1]
body[1,1..1,19]
component[1,1..1,19]
fragmentComponent[1,1..1,19]
FragmentOpen[1,1](<>)
body[1,3..1,16]
bodyText[1,3..1,16]
text[1,3..1,16]
RawText[1,3](Hello, World!)
FragmentClose[1,16](</>)
bodyText[1,19..1,19]
text[1,19..1,19]
RawText[1,19](\n)
EOF[2,1](<EOF>)

View File

@ -0,0 +1,12 @@
compilationUnit[1,1..1,16]
body[1,1..1,15]
bodyText[1,1..1,15]
text[1,1..1,8]
RawText[1,1](Hello, )
bodyTextGroovyElement[1,8..1,15]
dollarReference[1,8..1,15]
DollarReferenceStart[1,8]($)
GroovyCode[1,9](target)
text[1,15..1,15]
RawText[1,15](!)
EOF[1,16](<EOF>)

View File

@ -0,0 +1,6 @@
compilationUnit[1,1..2,1]
body[1,1..2,1]
bodyText[1,1..2,1]
text[1,1..2,1]
RawText[1,1](Hello, World!\n)
EOF[2,1](<EOF>)

View File

@ -1,5 +0,0 @@
---
class Greeting {
String myGreeting
}
---

View File

@ -1,5 +0,0 @@
compilationUnit[1,1..3,1]
preamble[1,1..2,1]
PreambleBreak[1,1](---\n)
PreambleBreak[2,1](---\n)
EOF[3,1](<EOF>)

View File

@ -1,6 +0,0 @@
compilationUnit[1,1..8,1]
preamble[1,1..6,1]
PreambleBreak[1,1](---\n)
GroovyCode[2,1](\n\n\n\n)
PreambleBreak[6,1](\n---\n)
EOF[8,1](<EOF>)

View File

@ -1,28 +0,0 @@
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

@ -1,13 +0,0 @@
compilationUnit[1,1..1,16]
body[1,1..1,15]
bodyText[1,1..1,15]
gStringBodyText[1,1..1,15]
jStringBodyText[1,1..1,1]
RawText[1,1](Hello, )
gStringBodyTextGroovyElement[1,8..1,9]
dollarReference[1,8..1,9]
DollarReferenceStart[1,8]($)
GroovyCode[1,9](target)
jStringBodyText[1,15..1,15]
RawText[1,15](!)
EOF[1,16](<EOF>)

View File

@ -1,6 +0,0 @@
compilationUnit[1,1..6,1]
preamble[1,1..4,2]
PreambleBreak[1,1](---\n)
GroovyCode[2,1](class Greeting {\n String myGreeting\n})
PreambleBreak[4,2](\n---\n)
EOF[6,1](<EOF>)

View File

@ -1,6 +1,5 @@
package groowt.view.component.web.ast; package groowt.view.component.web.ast;
import groowt.view.component.web.ast.extension.GStringNodeExtension;
import groowt.view.component.web.ast.node.*; import groowt.view.component.web.ast.node.*;
import groowt.view.component.web.util.TokenRange; import groowt.view.component.web.util.TokenRange;
import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.Token;
@ -75,14 +74,26 @@ public abstract class NodeFactoryTests {
} }
@Test @Test
public void gStringBodyTextNode(@Mock LeafNode child) { public void bodyTextNode(@Mock BodyTextChild child, @Mock TreeNode childAsNode) {
when(child.hasExtension(GStringNodeExtension.class)).thenReturn(true); when(child.asNode()).thenReturn(childAsNode);
assertNotNull(this.nodeFactory.gStringBodyTextNode(this.getTokenRange(), List.of(child))); assertNotNull(this.nodeFactory.bodyTextNode(this.getTokenRange(), List.of(child)));
} }
@Test @Test
public void jStringBodyTextNode() { public void questionTagNode(@Mock QuestionTagChild child, @Mock TreeNode childAsNode) {
assertNotNull(this.nodeFactory.jStringBodyTextNode(this.getTokenRange(), "Hello!")); when(child.asNode()).thenReturn(childAsNode);
assertNotNull(this.nodeFactory.questionTagNode(this.getTokenRange(), List.of(child)));
}
@Test
public void htmlCommentNode(@Mock HtmlCommentChild child, @Mock TreeNode childAsNode) {
when(child.asNode()).thenReturn(childAsNode);
assertNotNull(this.nodeFactory.htmlCommentNode(this.getTokenRange(), List.of(child)));
}
@Test
public void textNode() {
assertNotNull(this.nodeFactory.textNode(this.getTokenRange(), "Hello, World!"));
} }
@Test @Test

View File

@ -16,36 +16,36 @@ abstract class AbstractSourceTransformerCli : Callable<Int> {
protected lateinit var targets: List<Path> protected lateinit var targets: List<Path>
@Option( @Option(
names = ["--n", "--dry-run"], names = ["-i", "--interactive"],
description = ["Do a dry run; do not output files to disk."] description = ["Allow interactive recovery from errors. If false, implies -W."]
) )
protected var dryRun: Boolean = false protected var interactive = false
@Option( @Option(
names = ["-y", "--yes"], names = ["-P", "--print-only"],
description = ["Automatically write output files if there are no processing errors."] description = ["Only print result(s) to stdout; do not write files to disk."]
) )
protected var autoYes: Boolean = false protected var printOnly = false
@Option( @Option(
names = ["-W", "--write-over"], names = ["-W", "--write-over"],
description = ["If an output file already exists, write over it without asking."] description = ["If an output file already exists, write over it without asking."]
) )
protected var autoWriteOver: Boolean = false protected var autoWriteOver = false
@Option( @Option(
names = ["-v", "--verbose"], names = ["-v", "--verbose"],
description = ["Log verbosely to the console."] description = ["Log exceptions and errors verbosely to stderr."]
) )
protected var verbose: Boolean = false protected var verbose = false
private val scanner = Scanner(System.`in`) private val scanner = Scanner(System.`in`)
protected fun getYesNo(prompt: String, allowAuto: Boolean = true): Boolean { protected open fun getYesNo(prompt: String, fallback: Boolean): Boolean {
if (this.autoYes && allowAuto) { if (!interactive) {
return true return fallback
} else { } else {
print("$prompt (y/n) ") print("$prompt (y/n): ")
while (true) { while (true) {
if (this.scanner.hasNextLine()) { if (this.scanner.hasNextLine()) {
val input = this.scanner.nextLine() val input = this.scanner.nextLine()
@ -60,24 +60,21 @@ abstract class AbstractSourceTransformerCli : Callable<Int> {
} }
private fun doWrite(resolvedTarget: Path, text: String) { private fun doWrite(resolvedTarget: Path, text: String) {
if (this.dryRun) { if (!this.printOnly) {
println("Dry-run: would write to $resolvedTarget")
} else {
println("Writing to $resolvedTarget...")
Files.writeString(resolvedTarget, text) Files.writeString(resolvedTarget, text)
} }
} }
protected fun writeToDisk(target: Path, text: String) { protected open fun writeToDisk(target: Path, text: String) {
val outputDir = getOutputDir() val outputDir = getOutputDir()
if (outputDir != null) { if (outputDir != null) {
Files.createDirectories(outputDir) Files.createDirectories(outputDir)
} }
val resolvedTarget = outputDir?.resolve(target) ?: target val resolvedTarget = outputDir?.resolve(target) ?: target
if (Files.exists(resolvedTarget) && !autoWriteOver) { if (Files.exists(resolvedTarget) && !autoWriteOver) {
if (getYesNo("$resolvedTarget already exists. Write over?")) { if (getYesNo("$resolvedTarget already exists. Write over?", true)) {
doWrite(resolvedTarget, text) doWrite(resolvedTarget, text)
} else { } else if (interactive) {
println("Skipping writing to $resolvedTarget") println("Skipping writing to $resolvedTarget")
} }
} else { } else {

View File

@ -0,0 +1,161 @@
package groowt.view.component.web.tools
import groowt.view.component.web.antlr.*
import groowt.view.component.web.antlr.WebViewComponentsLexerBase.ERROR
import groowt.view.component.web.antlr.WebViewComponentsLexerBase.HIDDEN
import groowt.view.component.web.antlr.WebViewComponentsParser.CompilationUnitContext
import org.antlr.v4.runtime.CharStreams
import org.antlr.v4.runtime.ConsoleErrorListener
import picocli.CommandLine
import picocli.CommandLine.Option
import java.nio.file.Path
import kotlin.io.path.nameWithoutExtension
import kotlin.system.exitProcess
open class ParseWvc : AbstractSourceTransformerCli() {
companion object {
@JvmStatic
fun main(args: Array<String>) {
exitProcess(CommandLine(ParseWvc()).execute(*args))
}
}
@Option(
names = ["-s", "--suffix"],
description = ["The suffix (not extension!) to append to the output file."]
)
protected var suffix: String? = null
@Option(
names = ["-e", "--extension"],
description = ["The extension for output files."],
defaultValue = ".txt"
)
protected lateinit var extension: String
@Option(
names = ["-d", "--output-dir"],
description = ["The output directory."],
defaultValue = ".",
paramLabel = "outputDir"
)
protected lateinit var myOutputDir: Path
@Option(
names = ["--strict"],
description = ["If true, do not recover from syntax errors during parsing."],
negatable = true
)
protected var strict = true
protected open fun onLexerErrors(errors: List<LexerError>): Boolean {
System.err.println("There were lexer errors.")
errors.forEach { System.err.println(formatLexerError(it)) }
return this.getYesNo("Do you wish to try again?", false)
}
protected open fun onParserErrors(errors: List<ParserError>): Boolean {
System.err.println("There were parser errors.")
errors.forEach { System.err.println(formatParserError(it)) }
return this.getYesNo("Do you wish to try again?", false)
}
protected open fun getOutputPath(target: Path): Path =
Path.of(target.nameWithoutExtension + (suffix ?: "") + extension)
protected open fun onSuccess(
target: Path,
parser: WebViewComponentsParser,
cuContext: CompilationUnitContext
): Boolean {
val formatted = formatTree(
parser = parser,
tree = cuContext,
colors = false
)
if (this.interactive) {
println("Please review the following parse tree:\n$formatted")
} else {
println(formatted)
}
if (this.getYesNo("Write to disk?", true)) {
this.writeToDisk(getOutputPath(target), formatted)
return false
} else {
return this.getYesNo("Do you wish to redo this file?", false)
}
}
protected open fun onException(e: Exception): Boolean {
System.err.println("There was an exception during parsing: $e")
if (this.verbose) {
e.printStackTrace(System.err)
}
return this.getYesNo("Do you wish to try again?", false)
}
override fun getOutputDir() = myOutputDir
override fun transform(target: Path): Int {
if (interactive) {
println("Parsing $target")
}
var code = 0
while (true) {
try {
val input = CharStreams.fromPath(target)
val lexer = WebViewComponentsLexer(input)
lexer.removeErrorListener(ConsoleErrorListener.INSTANCE)
val lexerErrorListener = LexerErrorListener()
lexer.addErrorListener(lexerErrorListener)
val tokenStream = if (strict) {
WebViewComponentsTokenStream(lexer) // only ignore hidden (default)
} else {
WebViewComponentsTokenStream(lexer, setOf(HIDDEN, ERROR)) // ignore hidden and error
}
val parser = WebViewComponentsParser(tokenStream)
parser.removeErrorListener(ConsoleErrorListener.INSTANCE)
val parserErrorListener = ParserErrorListener()
parser.addErrorListener(parserErrorListener)
val cuContext = parser.compilationUnit()
val lexerErrors = lexerErrorListener.getErrors() + parserErrorListener.getLexerErrors()
val parserErrors = parserErrorListener.getParserErrors()
if (lexerErrors.isNotEmpty()) {
val recover = this.onLexerErrors(lexerErrors)
if (!recover) {
code = 1
break
}
} else if (parserErrors.isNotEmpty()) {
val recover = this.onParserErrors(parserErrors)
if (!recover) {
code = 1
break
}
} else {
val redo = this.onSuccess(target, parser, cuContext)
if (!redo) {
break
}
}
} catch (e: Exception) {
val recover = this.onException(e)
if (!recover) {
code = 1
break
}
}
}
return code
}
}

View File

@ -8,7 +8,6 @@ import picocli.CommandLine
import picocli.CommandLine.Command import picocli.CommandLine.Command
import picocli.CommandLine.Option import picocli.CommandLine.Option
import java.nio.file.Path import java.nio.file.Path
import java.util.*
import kotlin.io.path.nameWithoutExtension import kotlin.io.path.nameWithoutExtension
import kotlin.system.exitProcess import kotlin.system.exitProcess
@ -31,9 +30,9 @@ open class TokenizeWvc : AbstractSourceTransformerCli() {
@Option( @Option(
names = ["-s", "--suffix"], names = ["-s", "--suffix"],
description = ["The suffix (not extension!) to append to the output file names."] description = ["The suffix (not extension!) to append to output file names."]
) )
protected lateinit var suffix: Optional<String> protected var suffix: String? = null
@Option( @Option(
names = ["-e", "--extension"], names = ["-e", "--extension"],
@ -48,47 +47,46 @@ open class TokenizeWvc : AbstractSourceTransformerCli() {
defaultValue = ".", defaultValue = ".",
paramLabel = "outputDir" paramLabel = "outputDir"
) )
private var myOutputDir: Path? = null protected lateinit var myOutputDir: Path
protected fun onErrors(errors: List<LexerError>): Boolean { protected fun onErrors(errors: List<LexerError>): Boolean {
println("There were errors during tokenization.") System.err.println("There were errors during tokenization.")
errors.forEach { println(formatLexerError(it)) } errors.forEach { System.err.println(formatLexerError(it)) }
return this.getYesNo("Do you wish to try again?", false) return this.getYesNo("Do you wish to try again?", false)
} }
private fun getOutputPath(target: Path): Path = protected fun getOutputPath(target: Path): Path =
Path.of(target.nameWithoutExtension + suffix.orElse("") + extension) Path.of(target.nameWithoutExtension + (suffix ?: "") + extension)
protected fun onSuccess(target: Path, allTokens: List<Token>): Boolean { protected fun onSuccess(target: Path, allTokens: List<Token>): Boolean {
val formatted = allTokens.mapIndexed { index, token -> val formatted = allTokens.mapIndexed { index, token ->
"$index: ${formatToken(token)}" "$index: ${formatToken(token)}"
}.joinToString(separator = "\n") }.joinToString(separator = "\n")
if (!this.autoYes) { if (interactive) {
println("Please review the following tokens:\n$formatted") println("Please review the following tokens:\n$formatted")
if (this.getYesNo("Write to disk?")) { }
this.writeToDisk(getOutputPath(target), formatted) if (this.getYesNo("Write to disk?", true)) {
return false
} else {
return this.getYesNo("Do you wish to redo this file?", false)
}
} else {
this.writeToDisk(getOutputPath(target), formatted) this.writeToDisk(getOutputPath(target), formatted)
return false return false
} else {
return this.getYesNo("Do you wish to redo this file?", false)
} }
} }
protected fun onException(e: Exception): Boolean { protected fun onException(e: Exception): Boolean {
println("There was an exception during processing: $e") System.err.println("There was an exception during processing: $e")
if (this.verbose) { if (this.verbose) {
e.printStackTrace() e.printStackTrace(System.err)
} }
return this.getYesNo("Do you wish to try again?", false) return this.getYesNo("Do you wish to try again?", false)
} }
override fun getOutputDir(): Path? = myOutputDir override fun getOutputDir() = myOutputDir
override fun transform(target: Path): Int { override fun transform(target: Path): Int {
println("Processing $target") if (interactive) {
println("Tokenizing $target")
}
var code = 0 var code = 0
while (true) { while (true) {
try { try {
@ -99,7 +97,7 @@ open class TokenizeWvc : AbstractSourceTransformerCli() {
val errorListener = LexerErrorListener() val errorListener = LexerErrorListener()
lexer.addErrorListener(errorListener) lexer.addErrorListener(errorListener)
val tokenStream = WebViewComponentsTokenStream(lexer, setOf(WebViewComponentsLexer.HIDDEN)) val tokenStream = WebViewComponentsTokenStream(lexer, setOf()) // include everything (ignore nothing)
val allTokens = tokenStream.getAllTokensSkipEOF() val allTokens = tokenStream.getAllTokensSkipEOF()
val errors = errorListener.getErrors() val errors = errorListener.getErrors()