[ 
https://issues.apache.org/jira/browse/TINKERPOP-3183?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18144052#comment-18144052
 ] 

ASF GitHub Bot commented on TINKERPOP-3183:
-------------------------------------------

xiazcy commented on code in PR #3773:
URL: https://github.com/apache/tinkerpop/pull/3773#discussion_r4556708675


##########
gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/StatementParser.groovy:
##########
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.console
+
+import org.codehaus.groovy.control.CompilationUnit
+import org.codehaus.groovy.control.CompilerConfiguration
+import org.codehaus.groovy.control.MultipleCompilationErrorsException
+import org.codehaus.groovy.control.Phases
+import org.jline.reader.EOFError
+import org.jline.reader.ParsedLine
+import org.jline.reader.Parser
+import org.jline.reader.impl.DefaultParser
+
+import java.util.function.Predicate
+
+/**
+ * Decides when typed, piped or scripted input is a complete statement. The 
line reader's own parser only waits for
+ * more input while a bracket, quote or comment is open, so a line such as 
{@code g.V().} or {@code x = 1 +} would run
+ * at once and fail. This parser also waits when the statement only fails to 
compile because it ends too early.
+ */
+class StatementParser implements Parser {
+    private final Parser delegate
+    private final CompilerConfiguration config
+    private final Predicate<String> isCommand
+
+    /**
+     * @param delegate the line reader's parser
+     * @param config the compiler settings of the engine the statement runs in
+     * @param isCommand whether a name such as {@code /plugin} is a console 
command
+     */
+    StatementParser(final Parser delegate, final CompilerConfiguration config, 
final Predicate<String> isCommand) {
+        this.delegate = delegate
+        this.config = config
+        this.isCommand = isCommand
+    }
+
+    /**
+     * Creates a parser for script files, with the line reader settings 
groovysh uses for typed input.
+     */
+    static StatementParser forScripts(final CompilerConfiguration config) {
+        final DefaultParser lineParser = new DefaultParser(regexCommand: 
/\/?[a-zA-Z!]\S*/, eofOnUnclosedQuote: true,
+                eofOnEscapedNewLine: true)
+        lineParser.blockCommentDelims(new 
DefaultParser.BlockCommentDelims("/*", "*/"))
+                .lineCommentDelims(["//"] as String[])
+                .setEofOnUnclosedBracket(DefaultParser.Bracket.CURLY, 
DefaultParser.Bracket.ROUND, DefaultParser.Bracket.SQUARE)
+        // console commands in scripts are handled before a statement is parsed
+        return new StatementParser(lineParser, config, { false } as 
Predicate<String>)
+    }
+
+    @Override
+    ParsedLine parse(final String line, final int cursor, final ParseContext 
context) {
+        if (context != ParseContext.ACCEPT_LINE) return delegate.parse(line, 
cursor, context)
+
+        final boolean command = isCommandLine(line)
+        try {
+            final ParsedLine parsed = delegate.parse(line, cursor, context)
+            if (!command && continuesOnNextLine(line)) throw new 
StatementContinues()
+            return parsed
+        } catch (EOFError e) {
+            // more input cannot fix a stray closing bracket, and a command 
with an unclosed quote is reported by
+            // the command, so both are accepted as they are
+            if (e instanceof StatementContinues || !(command || 
hasStrayClosingBracket(line))) throw e
+            return delegate.parse(line, cursor, ParseContext.SPLIT_LINE)
+        }
+    }
+
+    /**
+     * Whether the text is an unfinished statement that the next line 
continues.
+     */
+    boolean isUnfinished(final String text) {
+        try {
+            parse(text, text.length(), ParseContext.ACCEPT_LINE)
+            return false
+        } catch (EOFError ignored) {
+            return true
+        }
+    }
+
+    private boolean isCommandLine(final String line) {
+        final String trimmed = line.trim()
+        if (!trimmed.startsWith(":") && !trimmed.startsWith("/")) return false
+        final String name = "/" + trimmed.substring(1).split(/\s+/, 2)[0]
+        return isCommand.test(name)
+    }
+
+    /**
+     * Whether the statement fails to parse only because it ends too early, 
e.g. after a trailing {@code .} or an
+     * operator: it does not parse as it is but does once a name is added 
after it. The position of Groovy's error
+     * cannot be used instead, as {@code repeat(out()).} is reported at the 
call rather than at the end.
+     */
+    boolean continuesOnNextLine(final String statement) {
+        final String code = statement.stripTrailing()
+        // ":" starts no Groovy statement, so an unknown command is a mistake 
rather than an unfinished line
+        if (code.isEmpty() || code.startsWith(":")) return false
+
+        return !parses(code) && parses(code + "\n" + NEXT_LINE)
+    }
+
+    /**
+     * Stands in for whatever the next line holds.
+     */
+    private static final String NEXT_LINE = "_"
+
+    private boolean parses(final String code) {
+        // parse only, so no class is generated or loaded
+        final CompilationUnit unit = new CompilationUnit(config)
+        unit.addSource("Script1.groovy", code)
+        try {
+            unit.compile(Phases.CONVERSION)
+            return true
+        } catch (MultipleCompilationErrorsException ignored) {
+            return false
+        }
+    }
+
+    /**
+     * Whether a closing bracket appears before any matching opening bracket, 
which no further input can fix.
+     * Brackets inside strings count too.
+     */
+    static boolean hasStrayClosingBracket(final String statement) {

Review Comment:
   hasStrayClosingBracket counts brackets inside strings, so x = [")", counts 
as finished. In the console it runs immediately and fails with Missing ']' 
instead of showing the ......1> continuation prompt.





> Upgrade to Groovy 6
> -------------------
>
>                 Key: TINKERPOP-3183
>                 URL: https://issues.apache.org/jira/browse/TINKERPOP-3183
>             Project: TinkerPop
>          Issue Type: Improvement
>          Components: groovy
>    Affects Versions: 3.7.4
>            Reporter: Stephen Mallette
>            Priority: Major
>
> -Groovy 5 recently released. While Groovy will be removed from Gremlin Server 
> in 4.x it will still exist for Gremlin Console (and perhaps other use cases). 
> Note that for Gremlin Console, there were supposedly significant changes to 
> {{groovysh}} for Groovy 5 because of the upgrade to JLine. It likely will 
> impact Gremlin Console and make the upgrade more complicated.-
> Groovy 6 has now released, which has walked back some of the changes which 
> had complicated the gremlin-console groovy 5 migration. We should aim to jump 
> straight to groovy 6.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to