You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by gn...@apache.org on 2018/04/19 18:07:32 UTC

svn commit: r1829590 - in /felix/trunk/gogo/jline/src: main/java/org/apache/felix/gogo/jline/ParsedLineImpl.java main/java/org/apache/felix/gogo/jline/Parser.java test/java/org/apache/felix/gogo/jline/ParserTest.java

Author: gnodet
Date: Thu Apr 19 18:07:32 2018
New Revision: 1829590

URL: http://svn.apache.org/viewvc?rev=1829590&view=rev
Log:
[FELIX-5833][gogo][jline] Fix parser to support quotes and escape characters

Added:
    felix/trunk/gogo/jline/src/test/java/org/apache/felix/gogo/jline/ParserTest.java
Modified:
    felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ParsedLineImpl.java
    felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Parser.java

Modified: felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ParsedLineImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ParsedLineImpl.java?rev=1829590&r1=1829589&r2=1829590&view=diff
==============================================================================
--- felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ParsedLineImpl.java (original)
+++ felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ParsedLineImpl.java Thu Apr 19 18:07:32 2018
@@ -18,14 +18,15 @@
  */
 package org.apache.felix.gogo.jline;
 
-import java.util.ArrayList;
-import java.util.List;
-
 import org.apache.felix.gogo.runtime.Parser.Program;
 import org.apache.felix.gogo.runtime.Token;
-import org.jline.reader.ParsedLine;
+import org.jline.reader.CompletingParsedLine;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
 
-public class ParsedLineImpl implements ParsedLine {
+public class ParsedLineImpl implements CompletingParsedLine {
 
     private final Program program;
     private final String source;
@@ -33,6 +34,8 @@ public class ParsedLineImpl implements P
     private final List<String> tokens;
     private final int wordIndex;
     private final int wordCursor;
+    private final CharSequence rawWord;
+    private final int rawWordCursor;
 
     public ParsedLineImpl(Program program, Token line, int cursor, List<Token> tokens) {
         this.program = program;
@@ -40,7 +43,7 @@ public class ParsedLineImpl implements P
         this.cursor = cursor - line.start();
         this.tokens = new ArrayList<>();
         for (Token token : tokens) {
-            this.tokens.add(token.toString());
+            this.tokens.add(unquote(token, null).toString());
         }
         int wi = tokens.size();
         int wc = 0;
@@ -62,9 +65,16 @@ public class ParsedLineImpl implements P
         }
         if (wi == tokens.size()) {
             this.tokens.add("");
+            rawWord = "";
+            wordCursor = 0;
+        } else {
+            rawWord = tokens.get(wi);
+            int[] c = new int[] { wc };
+            unquote(rawWord, c);
+            wordCursor = c[0];
         }
         wordIndex = wi;
-        wordCursor = wc;
+        rawWordCursor = wc;
     }
 
     public String word() {
@@ -94,4 +104,99 @@ public class ParsedLineImpl implements P
     public Program program() {
         return program;
     }
+
+    public int rawWordCursor() {
+        return rawWordCursor;
+    }
+
+    public int rawWordLength() {
+        return rawWord.length();
+    }
+
+    public CharSequence escape(CharSequence str, boolean complete) {
+        StringBuilder sb = new StringBuilder(str);
+        Predicate<Character> needToBeEscaped;
+        char quote = 0;
+        char first = rawWord.length() > 0 ? rawWord.charAt(0) : 0;
+        if (first == '\'') {
+            quote = '\'';
+            needToBeEscaped = i -> i == '\'';
+        } else if (first == '"') {
+            quote = '"';
+            needToBeEscaped = i -> i == '"';
+        } else {
+            needToBeEscaped = i -> i == ' ' || i == '\t';
+        }
+        for (int i = 0; i < sb.length(); i++) {
+            if (needToBeEscaped.test(str.charAt(i))) {
+                sb.insert(i++, '\\');
+            }
+        }
+        if (quote != 0) {
+            sb.insert(0, quote);
+            if (complete) {
+                sb.append(quote);
+            }
+        }
+        return sb;
+    }
+
+    private CharSequence unquote(CharSequence arg, int[] cursor) {
+        boolean hasEscape = false;
+        for (int i = 0; i < arg.length(); i++) {
+            int c = arg.charAt(i);
+            if (c == '\\' || c == '"' || c == '\'') {
+                hasEscape = true;
+                break;
+            }
+        }
+        if (!hasEscape) {
+            return arg;
+        }
+        boolean singleQuoted = false;
+        boolean doubleQuoted = false;
+        boolean escaped = false;
+        StringBuilder buf = new StringBuilder(arg.length());
+        for (int i = 0; i < arg.length(); i++) {
+            if (cursor != null && cursor[0] == i) {
+                cursor[0] = buf.length();
+                cursor = null;
+            }
+            char c = arg.charAt(i);
+            if (doubleQuoted && escaped) {
+                if (c != '"' && c != '\\' && c != '$' && c != '%') {
+                    buf.append('\\');
+                }
+                buf.append(c);
+                escaped = false;
+            } else if (escaped) {
+                buf.append(c);
+                escaped = false;
+            } else if (singleQuoted) {
+                if (c == '\'') {
+                    singleQuoted = false;
+                } else {
+                    buf.append(c);
+                }
+            } else if (doubleQuoted) {
+                if (c == '\\') {
+                    escaped = true;
+                } else if (c == '\"') {
+                    doubleQuoted = false;
+                } else {
+                    buf.append(c);
+                }
+            } else if (c == '\\') {
+                escaped = true;
+            } else if (c == '\'') {
+                singleQuoted = true;
+            } else if (c == '"') {
+                doubleQuoted = true;
+            } else {
+                buf.append(c);
+            }
+        }
+        return buf.toString();
+    }
+
 }

Modified: felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Parser.java
URL: http://svn.apache.org/viewvc/felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Parser.java?rev=1829590&r1=1829589&r2=1829590&view=diff
==============================================================================
--- felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Parser.java (original)
+++ felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Parser.java Thu Apr 19 18:07:32 2018
@@ -18,6 +18,7 @@
  */
 package org.apache.felix.gogo.jline;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
@@ -32,7 +33,7 @@ public class Parser implements org.jline
 
     public ParsedLine parse(String line, int cursor, ParseContext context) throws org.jline.reader.SyntaxError {
         try {
-            return doParse(line, cursor);
+            return doParse(line, cursor, context);
         } catch (EOFError e) {
             throw new org.jline.reader.EOFError(e.line(), e.column(), e.getMessage(), e.missing());
         } catch (SyntaxError e) {
@@ -40,10 +41,24 @@ public class Parser implements org.jline
         }
     }
 
-    private ParsedLine doParse(CharSequence line, int cursor) throws SyntaxError {
-        org.apache.felix.gogo.runtime.Parser parser = new org.apache.felix.gogo.runtime.Parser(line);
-        Program program = parser.program();
-        List<Statement> statements = parser.statements();
+    private ParsedLine doParse(String line, int cursor, ParseContext parseContext) throws SyntaxError {
+        Program program = null;
+        List<Statement> statements = null;
+        String repaired = line;
+        while (program == null) {
+            try {
+                org.apache.felix.gogo.runtime.Parser parser = new org.apache.felix.gogo.runtime.Parser(repaired);
+                program = parser.program();
+                statements = parser.statements();
+            } catch (EOFError e) {
+                // Make sure we don't loop forever
+                if (parseContext == ParseContext.COMPLETE && repaired.length() < line.length() + 1024) {
+                    repaired = repaired + " " + e.repair();
+                } else {
+                    throw e;
+                }
+            }
+        }
         // Find corresponding statement
         Statement statement = null;
         for (int i = statements.size() - 1; i >= 0; i--) {
@@ -60,7 +75,14 @@ public class Parser implements org.jline
                 break;
             }
         }
-        if (statement != null) {
+        if (statement != null && statement.tokens() != null && !statement.tokens().isEmpty()) {
+            if (repaired != line) {
+                Token stmt = statement.subSequence(0, line.length() - statement.start());
+                List<Token> tokens = new ArrayList<>(statement.tokens());
+                Token last = tokens.get(tokens.size() - 1);
+                tokens.set(tokens.size() - 1, last.subSequence(0, line.length() - last.start()));
+                return new ParsedLineImpl(program, stmt, cursor, tokens);
+            }
             return new ParsedLineImpl(program, statement, cursor, statement.tokens());
         } else {
             // TODO:

Added: felix/trunk/gogo/jline/src/test/java/org/apache/felix/gogo/jline/ParserTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/gogo/jline/src/test/java/org/apache/felix/gogo/jline/ParserTest.java?rev=1829590&view=auto
==============================================================================
--- felix/trunk/gogo/jline/src/test/java/org/apache/felix/gogo/jline/ParserTest.java (added)
+++ felix/trunk/gogo/jline/src/test/java/org/apache/felix/gogo/jline/ParserTest.java Thu Apr 19 18:07:32 2018
@@ -0,0 +1,59 @@
+/*
+ * 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.felix.gogo.jline;
+
+import org.jline.reader.CompletingParsedLine;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class ParserTest {
+
+    @Test
+    public void testEscapedWord() {
+        Parser parser = new Parser();
+        CompletingParsedLine line = (CompletingParsedLine) parser.parse("foo second\\ param \"quoted param\"", 15);
+        assertNotNull(line);
+        assertNotNull(line.words());
+        assertEquals("foo second\\ param \"quoted param\"", line.line());
+        assertEquals(15, line.cursor());
+        assertEquals(3, line.words().size());
+        assertEquals("second param", line.word());
+        assertEquals(10, line.wordCursor());
+        assertEquals(11, line.rawWordCursor());
+        assertEquals(13, line.rawWordLength());
+    }
+
+    @Test
+    public void testQuotedWord() {
+        Parser parser = new Parser();
+        CompletingParsedLine line = (CompletingParsedLine) parser.parse("foo second\\ param \"quoted param\"", 20);
+        assertNotNull(line);
+        assertNotNull(line.words());
+        assertEquals("foo second\\ param \"quoted param\"", line.line());
+        assertEquals(20, line.cursor());
+        assertEquals(3, line.words().size());
+        assertEquals("quoted param", line.word());
+        assertEquals(1, line.wordCursor());
+        assertEquals(2, line.rawWordCursor());
+        assertEquals(14, line.rawWordLength());
+    }
+
+}