From 63720693a379f9f72bc0bd3b97fc6ce219b82c1a Mon Sep 17 00:00:00 2001 From: JesseBrault0709 <62299747+JesseBrault0709@users.noreply.github.com> Date: Fri, 17 May 2024 08:54:36 +0200 Subject: [PATCH] Groovyc can now accept .wvc files. --- .../ComponentTemplateCompileException.java | 4 +- web-views/build.gradle | 10 + web-views/groovycTest | 17 ++ ...ViewComponentTemplateCompileException.java | 23 ++- .../groovyc/DelegatingWvcParserPlugin.java | 184 ++++++++++++++++++ .../web/groovyc/WvcParserPluginFactory.java | 14 ++ .../org.codehaus.groovy.source.Extensions | 1 + .../groovyc/GroovycConfigurationScript.groovy | 1 + 8 files changed, 246 insertions(+), 8 deletions(-) create mode 100755 web-views/groovycTest create mode 100644 web-views/src/main/java/groowt/view/web/groovyc/DelegatingWvcParserPlugin.java create mode 100644 web-views/src/main/java/groowt/view/web/groovyc/WvcParserPluginFactory.java create mode 100644 web-views/src/main/resources/META-INF/services/org.codehaus.groovy.source.Extensions create mode 100644 web-views/src/main/resources/groowt/view/web/groovyc/GroovycConfigurationScript.groovy diff --git a/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompileException.java b/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompileException.java index d5e1d4e..eae8051 100644 --- a/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompileException.java +++ b/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompileException.java @@ -23,14 +23,14 @@ public class ComponentTemplateCompileException extends Exception { @Override public String getMessage() { final var sb = new StringBuilder("Error in ").append(compileUnit.getSource().getDescription()); - final @Nullable String position = this.getPosition(); + final @Nullable String position = this.formatPosition(); if (position != null) { sb.append(" at ").append(position); } return sb.append(": ").append(super.getMessage()).toString(); } - protected @Nullable String getPosition() { + protected @Nullable String formatPosition() { return null; } diff --git a/web-views/build.gradle b/web-views/build.gradle index 989a1bb..df7c771 100644 --- a/web-views/build.gradle +++ b/web-views/build.gradle @@ -133,6 +133,16 @@ tasks.register('toolsJar', Jar) { dependsOn tasks.named('jar') } +tasks.register('uberJar', Jar) { + group = 'build' + archiveBaseName = 'web-views-uber' + from sourceSets.main.output + from sourceSets.main.runtimeClasspath.filter(File.&exists).collect { + it.isDirectory() ? it : zipTree(it) + } + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} + @NullCheck class ToolSpec { diff --git a/web-views/groovycTest b/web-views/groovycTest new file mode 100755 index 0000000..4066b93 --- /dev/null +++ b/web-views/groovycTest @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +if [ "$1" == "--debug" ]; then + gradle -q uberJar && \ + java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:8192 \ + -cp build/libs/web-views-uber-0.1.0.jar \ + org.codehaus.groovy.tools.FileSystemCompiler \ + --configscript src/main/resources/groowt/view/web/groovyc/GroovycConfigurationScript.groovy \ + -d groovyc-out \ + sketching/helloTarget.wvc +else + gradle -q uberJar && \ + groovyc -cp build/libs/web-views-uber-0.1.0.jar \ + --configscript src/main/resources/groowt/view/web/groovyc/GroovycConfigurationScript.groovy \ + -d groovyc-out \ + sketching/helloTarget.wvc +fi diff --git a/web-views/src/main/java/groowt/view/web/compiler/WebViewComponentTemplateCompileException.java b/web-views/src/main/java/groowt/view/web/compiler/WebViewComponentTemplateCompileException.java index f7b158b..20a5ff8 100644 --- a/web-views/src/main/java/groowt/view/web/compiler/WebViewComponentTemplateCompileException.java +++ b/web-views/src/main/java/groowt/view/web/compiler/WebViewComponentTemplateCompileException.java @@ -2,8 +2,8 @@ package groowt.view.web.compiler; import groowt.view.component.compiler.ComponentTemplateCompileException; import groowt.view.component.compiler.ComponentTemplateCompileUnit; -import groowt.view.web.antlr.TokenUtil; import groowt.view.web.ast.node.Node; +import groowt.view.web.util.SourcePosition; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.tree.TerminalNode; import org.jetbrains.annotations.Nullable; @@ -60,14 +60,25 @@ public class WebViewComponentTemplateCompileException extends ComponentTemplateC this.node = node; } - @Override - protected @Nullable String getPosition() { + public @Nullable SourcePosition getSourcePosition() { if (this.node != null) { - return this.node.getTokenRange().getStartPosition().toStringLong(); + return this.node.getTokenRange().getStartPosition(); } else if (this.parserRuleContext != null) { - return TokenUtil.formatTokenPosition(this.parserRuleContext.start); + final var start = this.parserRuleContext.start; + return new SourcePosition(start.getLine(), start.getCharPositionInLine() + 1); } else if (this.terminalNode != null) { - return TokenUtil.formatTokenPosition(terminalNode.getSymbol()); + final var symbol = terminalNode.getSymbol(); + return new SourcePosition(symbol.getLine(), symbol.getCharPositionInLine() + 1); + } else { + return null; + } + } + + @Override + protected @Nullable String formatPosition() { + final SourcePosition sourcePosition = this.getSourcePosition(); + if (sourcePosition != null) { + return sourcePosition.toStringLong(); } else { return null; } diff --git a/web-views/src/main/java/groowt/view/web/groovyc/DelegatingWvcParserPlugin.java b/web-views/src/main/java/groowt/view/web/groovyc/DelegatingWvcParserPlugin.java new file mode 100644 index 0000000..b1f793f --- /dev/null +++ b/web-views/src/main/java/groowt/view/web/groovyc/DelegatingWvcParserPlugin.java @@ -0,0 +1,184 @@ +package groowt.view.web.groovyc; + +import groowt.view.component.compiler.ComponentTemplateCompileException; +import groowt.view.component.compiler.DefaultComponentTemplateCompilerConfiguration; +import groowt.view.component.compiler.source.ComponentTemplateSource; +import groowt.view.web.analysis.MismatchedComponentTypeAnalysis; +import groowt.view.web.analysis.MismatchedComponentTypeError; +import groowt.view.web.antlr.*; +import groowt.view.web.ast.DefaultAstBuilder; +import groowt.view.web.ast.DefaultNodeFactory; +import groowt.view.web.ast.node.CompilationUnitNode; +import groowt.view.web.compiler.AnonymousWebViewComponent; +import groowt.view.web.compiler.MultipleWebViewComponentCompileErrorsException; +import groowt.view.web.compiler.WebViewComponentTemplateCompileException; +import groowt.view.web.compiler.WebViewComponentTemplateCompileUnit; +import groowt.view.web.transpile.DefaultGroovyTranspiler; +import groowt.view.web.util.SourcePosition; +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.tree.TerminalNode; +import org.antlr.v4.runtime.tree.Tree; +import org.apache.groovy.parser.antlr4.Antlr4ParserPlugin; +import org.codehaus.groovy.ast.ModuleNode; +import org.codehaus.groovy.control.CompilationFailedException; +import org.codehaus.groovy.control.ParserPlugin; +import org.codehaus.groovy.control.SourceUnit; +import org.codehaus.groovy.syntax.ParserException; +import org.codehaus.groovy.syntax.Reduction; + +import java.io.IOException; +import java.io.Reader; +import java.util.List; + +public class DelegatingWvcParserPlugin implements ParserPlugin { + + private final Antlr4ParserPlugin groovyParserPlugin; + + public DelegatingWvcParserPlugin(Antlr4ParserPlugin groovyParserPlugin) { + this.groovyParserPlugin = groovyParserPlugin; + } + + @Override + public Reduction parseCST(SourceUnit sourceUnit, Reader reader) throws CompilationFailedException { + return this.groovyParserPlugin.parseCST(sourceUnit, reader); // returns null + } + + protected WebViewComponentTemplateCompileException getException( + WebViewComponentTemplateCompileUnit compileUnit, + TerminalNode terminalNode + ) { + final Token offending = terminalNode.getSymbol(); + final var exception = new WebViewComponentTemplateCompileException( + compileUnit, "Invalid token '" + TokenUtil.excerptToken(offending) + "'." + ); + exception.setTerminalNode(terminalNode); + return exception; + } + + protected WebViewComponentTemplateCompileException getException( + WebViewComponentTemplateCompileUnit compileUnit, + ParserRuleContext parserRuleContext + ) { + final var exception = new WebViewComponentTemplateCompileException( + compileUnit, + "Parser error: " + parserRuleContext.exception.getMessage(), + parserRuleContext.exception + ); + exception.setParserRuleContext(parserRuleContext); + return exception; + } + + protected WebViewComponentTemplateCompileException getException( + WebViewComponentTemplateCompileUnit compileUnit, + Tree tree + ) { + if (tree instanceof ParserRuleContext parserRuleContext) { + return getException(compileUnit, parserRuleContext); + } else if (tree instanceof TerminalNode terminalNode) { + return getException(compileUnit, terminalNode); + } else { + return new WebViewComponentTemplateCompileException( + compileUnit, + "Error at parser/lexer node " + tree.toString() + ); + } + } + + protected WebViewComponentTemplateCompileException getException( + WebViewComponentTemplateCompileUnit compileUnit, + MismatchedComponentTypeError error + ) { + final var exception = new WebViewComponentTemplateCompileException( + compileUnit, + error.getMessage() + ); + exception.setParserRuleContext(error.getComponent()); + return exception; + } + + protected ParserException translateException(ComponentTemplateCompileException e) { + final var actual = (WebViewComponentTemplateCompileException) e; + final SourcePosition sourcePosition = actual.getSourcePosition(); + if (sourcePosition != null) { + return new ParserException(e.getMessage(), e, sourcePosition.line(), sourcePosition.column()); + } else { + return new ParserException(e.getMessage(), e, -1, -1); + } + } + + @Override + public ModuleNode buildAST(SourceUnit sourceUnit, ClassLoader classLoader, Reduction cst) throws ParserException { + final String sourceUnitName = sourceUnit.getName(); + if (sourceUnitName.endsWith(".wvc")) { + final var compileUnit = new WebViewComponentTemplateCompileUnit( + AnonymousWebViewComponent.class, + ComponentTemplateSource.of(sourceUnit.getSource().getURI()), + "groowt.view.web.groovyc" + ); + + final CompilationUnitParseResult parseResult; + try { + parseResult = ParserUtil.parseCompilationUnit(sourceUnit.getSource().getReader()); + } catch (IOException e) { + throw new RuntimeException(e); + } + + // check for parser/lexer errors + final var parseErrors = AntlrUtil.findErrorNodes(parseResult.getCompilationUnitContext()); + if (!parseErrors.isEmpty()) { + if (parseErrors.getErrorCount() == 1) { + final var errorNode = parseErrors.getAll().getFirst(); + throw this.translateException(getException(compileUnit, errorNode)); + } else { + final var errorExceptions = parseErrors.getAll().stream() + .map(errorNode -> getException(compileUnit, errorNode)) + .toList(); + throw this.translateException(new MultipleWebViewComponentCompileErrorsException( + compileUnit, + errorExceptions + )); + } + } + + // check for mismatched type errors + final List mismatchedComponentTypeErrors = + MismatchedComponentTypeAnalysis.check(parseResult.getCompilationUnitContext()); + + if (!mismatchedComponentTypeErrors.isEmpty()) { + if (mismatchedComponentTypeErrors.size() == 1) { + throw new RuntimeException(getException(compileUnit, mismatchedComponentTypeErrors.getFirst())); + } else { + final var errorExceptions = mismatchedComponentTypeErrors.stream() + .map(error -> getException(compileUnit, error)) + .toList(); + throw this.translateException(new MultipleWebViewComponentCompileErrorsException( + compileUnit, + errorExceptions + )); + } + } + + // build ast + final var tokenList = new TokenList(parseResult.getTokenStream()); + final var astBuilder = new DefaultAstBuilder(new DefaultNodeFactory(tokenList)); + final var cuNode = (CompilationUnitNode) astBuilder.build(parseResult.getCompilationUnitContext()); + + final var groovyTranspiler = new DefaultGroovyTranspiler(); + try { + final SourceUnit transpiledSourceUnit = groovyTranspiler.transpile( + new DefaultComponentTemplateCompilerConfiguration(), + compileUnit, + cuNode, + sourceUnitName.substring(0, sourceUnitName.length() - 4) + ); + return transpiledSourceUnit.getAST(); + } catch (ComponentTemplateCompileException e) { + throw this.translateException(e); + } + } else { + return this.groovyParserPlugin.buildAST(sourceUnit, classLoader, cst); + } + } + +} diff --git a/web-views/src/main/java/groowt/view/web/groovyc/WvcParserPluginFactory.java b/web-views/src/main/java/groowt/view/web/groovyc/WvcParserPluginFactory.java new file mode 100644 index 0000000..9e209e7 --- /dev/null +++ b/web-views/src/main/java/groowt/view/web/groovyc/WvcParserPluginFactory.java @@ -0,0 +1,14 @@ +package groowt.view.web.groovyc; + +import org.apache.groovy.parser.antlr4.Antlr4ParserPlugin; +import org.codehaus.groovy.control.ParserPlugin; +import org.codehaus.groovy.control.ParserPluginFactory; + +public class WvcParserPluginFactory extends ParserPluginFactory { + + @Override + public ParserPlugin createParserPlugin() { + return new DelegatingWvcParserPlugin(new Antlr4ParserPlugin()); + } + +} diff --git a/web-views/src/main/resources/META-INF/services/org.codehaus.groovy.source.Extensions b/web-views/src/main/resources/META-INF/services/org.codehaus.groovy.source.Extensions new file mode 100644 index 0000000..e7610e3 --- /dev/null +++ b/web-views/src/main/resources/META-INF/services/org.codehaus.groovy.source.Extensions @@ -0,0 +1 @@ +.wvc diff --git a/web-views/src/main/resources/groowt/view/web/groovyc/GroovycConfigurationScript.groovy b/web-views/src/main/resources/groowt/view/web/groovyc/GroovycConfigurationScript.groovy new file mode 100644 index 0000000..357d2f6 --- /dev/null +++ b/web-views/src/main/resources/groowt/view/web/groovyc/GroovycConfigurationScript.groovy @@ -0,0 +1 @@ +configuration.pluginFactory = new groowt.view.web.groovyc.WvcParserPluginFactory()