Hi all,

I'm working on upgrading Apache TinkerPop from Groovy 4.0.32 to 5.1.2.
TinkerPop ships the Gremlin Console, the primary REPL for the Gremlin
language, and it is built by embedding groovysh. The Groovy 5 rewrite onto
JLine 3 removed the extension points we depended on. I'd like to discuss
what a supported embedding path could look like and whether the Groovy
community would accept this change for Groovy 5 or defer to Groovy 6.

The Gremlin Console preloads the Gremlin DSL so users can type
"g.V().out()" directly. That is 63 imports: 40 package star imports and 23
"import static X.*". In Groovy 4 we installed them as an ImportCustomizer
on Groovysh's CompilerConfiguration, and we added an
ASTTransformationCustomizer(ThreadInterrupt) so ctrl+C can interrupt a
long-running query.

In 5.1.2, GroovyEngine creates its shell as

    new GroovyShell(classLoader, sharedData)

at GroovyEngine.java:171, with no configuration parameter, and the shell
field is private final. CompilerConfiguration.DEFAULT is immutable in 5, so
there is no indirect route either.

The only supported alternative is executing import statements, which
GroovyEngine records as snippets and textually re-prepends to every
evaluation but as we've talked about previously on this list, this can
lead to a substantial performance loss.

------------------------------

Main.start(Map<String,?>, String...) accepts only binding variables, and
the body hardcodes the terminal, parser, engine, registries, prompt, banner
and REPL loop. We need to register our own commands, render results our own
way (Gremlin auto-iterates result sets with a user-configurable cap), and
control the prompt and error rendering. Today that means copying roughly
230 lines of Main into TinkerPop and re-diffing it against every Groovy
release.

Since start already takes a Map, the smallest version of this could be a
few recognized keys: extra CommandRegistry instances, a prompt supplier, a
result handler, an error handler, a banner toggle.

------------------------------

Main.ExtraConsoleCommands provides /clear /pwd /cd /ls /cat /grep /head
/tail /wc /sort /date /echo and /!. It is protected static nested in Main.
It currently compiles from another package only because Groovy does not
enforce protected access at compile time, which would not hold from Java or
under CompileStatic. Anyone embedding the REPL has to copy it.

------------------------------

Our :install command uses Grape plus ServiceLoader against the shell's
classloader so freshly downloaded plugins load without a restart. The
classLoader field is protected, so subclassing works today. A getter would
just avoid requiring a subclass in order to read one field.

To recap, I have several asks:
Ask 1: add GroovyEngine(CompilerConfiguration), or a protected GroovyShell
createShell(ClassLoader, Binding) seam that a subclass can override.
Ask 2: embedding hooks on Main
Ask 3: make Main.ExtraConsoleCommands a public top-level class
Ask 4: a public accessor for the engine's classloader (minor)

Please let me know if I'm going about this the wrong way and there are
actually already extension points that I should be using instead.

Thanks,
Ken

Reply via email to