You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by gn...@apache.org on 2010/09/03 21:51:44 UTC

svn commit: r992437 - in /karaf/trunk/shell/console/src: main/java/org/apache/karaf/shell/console/completer/ main/java/org/apache/karaf/shell/console/jline/ test/java/org/apache/karaf/shell/console/completer/

Author: gnodet
Date: Fri Sep  3 19:51:43 2010
New Revision: 992437

URL: http://svn.apache.org/viewvc?rev=992437&view=rev
Log:
KARAF-187: The completers do not work when several commands are on the same line separated by a column or pipe

Added:
    karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/Parser.java
    karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/console/completer/
    karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/console/completer/ArgumentCompleterTest.java
Removed:
    karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/SessionScopeCompleter.java
Modified:
    karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/ArgumentCompleter.java
    karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/CommandsCompleter.java
    karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/jline/Console.java

Modified: karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/ArgumentCompleter.java
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/ArgumentCompleter.java?rev=992437&r1=992436&r2=992437&view=diff
==============================================================================
--- karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/ArgumentCompleter.java (original)
+++ karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/ArgumentCompleter.java Fri Sep  3 19:51:43 2010
@@ -24,7 +24,8 @@
  */
 package org.apache.karaf.shell.console.completer;
 
-import java.util.*;
+import java.util.LinkedList;
+import java.util.List;
 
 import org.apache.karaf.shell.console.Completer;
 
@@ -62,7 +63,7 @@ public class ArgumentCompleter implement
      *  @param  completers  the embedded argument completers
      */
     public ArgumentCompleter(final Completer[] completers) {
-        this(completers, new WhitespaceArgumentDelimiter());
+        this(completers, new GogoArgumentDelimiter());
     }
 
     /**
@@ -210,69 +211,32 @@ public class ArgumentCompleter implement
     }
 
     /**
-     *  Abstract implementation of a delimiter that uses the
-     *  {@link #isDelimiter} method to determine if a particular
-     *  character should be used as a delimiter.
+     *  Implementation of a delimiter that uses the
+     *  Gogo parser.
      *
-     *  @author  <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
+     *  @author  <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
      */
-    public abstract static class AbstractArgumentDelimiter
-        implements ArgumentDelimiter {
-        private char[] quoteChars = new char[] { '\'', '"' };
-        private char[] escapeChars = new char[] { '\\' };
-
-        public void setQuoteChars(final char[] quoteChars) {
-            this.quoteChars = quoteChars;
-        }
-
-        public char[] getQuoteChars() {
-            return this.quoteChars;
-        }
-
-        public void setEscapeChars(final char[] escapeChars) {
-            this.escapeChars = escapeChars;
-        }
-
-        public char[] getEscapeChars() {
-            return this.escapeChars;
-        }
+    public static class GogoArgumentDelimiter implements ArgumentDelimiter {
 
         public ArgumentList delimit(final String buffer, final int cursor) {
-            List<String> args = new LinkedList<String>();
-            StringBuffer arg = new StringBuffer();
-            int argpos = -1;
-            int bindex = -1;
-
-            for (int i = 0; (buffer != null) && (i <= buffer.length()); i++) {
-                // once we reach the cursor, set the
-                // position of the selected index
-                if (i == cursor) {
-                    bindex = args.size();
-                    // the position in the current argument is just the
-                    // length of the current argument
-                    argpos = arg.length();
-                }
-
-                if ((i == buffer.length()) || isDelimiter(buffer, i)) {
-                    if (arg.length() > 0) {
-                        args.add(arg.toString());
-                        arg.setLength(0); // reset the arg
-                    }
-                } else {
-                    arg.append(buffer.charAt(i));
+            Parser parser = new Parser(buffer, cursor);
+            try {
+                List<List<List<CharSequence>>> program = parser.program();
+                List<CharSequence> pipe = program.get(parser.c0).get(parser.c1);
+                List<String> args = new LinkedList<String>();
+                for (CharSequence arg : pipe) {
+                    args.add(arg.toString());
                 }
+                return new ArgumentList(args.toArray(new String[args.size()]), parser.c2, parser.c3, cursor);
+            } catch (Throwable t) {
+                return new ArgumentList(new String[] { buffer }, 0, cursor, cursor);
             }
-
-            return new ArgumentList(args.
-                toArray(new String[args.size()]), bindex, argpos, cursor);
         }
 
         /**
          *  Returns true if the specified character is a whitespace
          *  parameter. Check to ensure that the character is not
-         *  escaped by any of
-         *  {@link #getQuoteChars}, and is not escaped by ant of the
-         *  {@link #getEscapeChars}, and returns true from
+         *  escaped and returns true from
          *  {@link #isDelimiterChar}.
          *
          *  @param  buffer the complete command buffer
@@ -280,64 +244,21 @@ public class ArgumentCompleter implement
          *  @return        true if the character should be a delimiter
          */
         public boolean isDelimiter(final String buffer, final int pos) {
-            if (isQuoted(buffer, pos)) {
-                return false;
-            }
-
-            if (isEscaped(buffer, pos)) {
-                return false;
-            }
-
-            return isDelimiterChar(buffer, pos);
-        }
-
-        public boolean isQuoted(final String buffer, final int pos) {
-            return false;
+            return !isEscaped(buffer, pos) && isDelimiterChar(buffer, pos);
         }
 
         public boolean isEscaped(final String buffer, final int pos) {
-            if (pos <= 0) {
-                return false;
-            }
-
-            for (int i = 0; (escapeChars != null) && (i < escapeChars.length);
-                     i++) {
-                if (buffer.charAt(pos) == escapeChars[i]) {
-                    return !isEscaped(buffer, pos - 1); // escape escape
-                }
-            }
-
-            return false;
+            return pos > 0 && buffer.charAt(pos) == '\\' && !isEscaped(buffer, pos - 1);
         }
 
         /**
-         *  Returns true if the character at the specified position
-         *  if a delimiter. This method will only be called if the
-         *  character is not enclosed in any of the
-         *  {@link #getQuoteChars}, and is not escaped by ant of the
-         *  {@link #getEscapeChars}. To perform escaping manually,
-         *  override {@link #isDelimiter} instead.
-         */
-        public abstract boolean isDelimiterChar(String buffer, int pos);
-    }
-
-    /**
-     *  {@link ArgumentCompleter.ArgumentDelimiter}
-     *  implementation that counts all
-     *  whitespace (as reported by {@link Character#isWhitespace})
-     *  as being a delimiter.
-     *
-     *  @author  <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
-     */
-    public static class WhitespaceArgumentDelimiter
-        extends AbstractArgumentDelimiter {
-        /**
          *  The character is a delimiter if it is whitespace, and the
          *  preceeding character is not an escape character.
          */
         public boolean isDelimiterChar(String buffer, int pos) {
             return Character.isWhitespace(buffer.charAt(pos));
         }
+
     }
 
     /**

Modified: karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/CommandsCompleter.java
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/CommandsCompleter.java?rev=992437&r1=992436&r2=992437&view=diff
==============================================================================
--- karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/CommandsCompleter.java (original)
+++ karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/CommandsCompleter.java Fri Sep  3 19:51:43 2010
@@ -67,7 +67,7 @@ public class CommandsCompleter implement
                 function = unProxy(function);
                 if (function instanceof AbstractCommand) {
                     List<Completer> cl = new ArrayList<Completer>();
-                    cl.add(new StringsCompleter(new String[] { command }));
+                    cl.add(new StringsCompleter(getNames(command)));
                     if (function instanceof CompletableFunction) {
                         List<Completer> fcl = ((CompletableFunction) function).getCompleters();
                         if (fcl != null) {
@@ -87,6 +87,11 @@ public class CommandsCompleter implement
         }
     }
 
+    private String[] getNames(String command) {
+        String[] s = command.split(":");
+        return new String[] { command, s[1] };
+    }
+
     protected Function unProxy(Function function) {
         try {
             if (function instanceof CommandProxy) {

Added: karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/Parser.java
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/Parser.java?rev=992437&view=auto
==============================================================================
--- karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/Parser.java (added)
+++ karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/Parser.java Fri Sep  3 19:51:43 2010
@@ -0,0 +1,472 @@
+/*
+ * 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.
+ */
+// DWB14: parser loops if // comment at start of program
+// DWB15: allow program to have trailing ';'
+package org.apache.karaf.shell.console.completer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Parser
+{
+    int current = 0;
+    CharSequence text;
+    boolean escaped;
+    static final String SPECIAL = "<;|{[\"'$`(=";
+
+    List<List<List<CharSequence>>> program;
+    List<List<CharSequence>> statements;
+    List<CharSequence> statement;
+    int cursor;
+    int start = -1;
+    int c0;
+    int c1;
+    int c2;
+    int c3;
+
+    public Parser(CharSequence text, int cursor)
+    {
+        this.text = text;
+        this.cursor = cursor;
+    }
+
+    void ws()
+    {
+        // derek: BUGFIX: loop if comment  at beginning of input
+        //while (!eof() && Character.isWhitespace(peek())) {
+        while (!eof() && (!escaped && Character.isWhitespace(peek()) || current == 0))
+        {
+            if (current != 0 || !escaped && Character.isWhitespace(peek()))
+            {
+                current++;
+            }
+            if (peek() == '/' && current < text.length() - 2
+                && text.charAt(current + 1) == '/')
+            {
+                comment();
+            }
+            if (current == 0)
+            {
+                break;
+            }
+        }
+    }
+
+    private void comment()
+    {
+        while (!eof() && peek() != '\n' && peek() != '\r')
+        {
+            next();
+        }
+    }
+
+    boolean eof()
+    {
+        return current >= text.length();
+    }
+
+    char peek()
+    {
+        return peek(false);
+    }
+
+    char peek(boolean increment)
+    {
+        escaped = false;
+        if (eof())
+        {
+            return 0;
+        }
+
+        int last = current;
+        char c = text.charAt(current++);
+
+        if (c == '\\')
+        {
+            escaped = true;
+            if (eof())
+            {
+                throw new RuntimeException("Eof found after \\"); // derek
+            }
+
+            c = text.charAt(current++);
+
+            switch (c)
+            {
+                case 't':
+                    c = '\t';
+                    break;
+                case '\r':
+                case '\n':
+                    c = ' ';
+                    break;
+                case 'b':
+                    c = '\b';
+                    break;
+                case 'f':
+                    c = '\f';
+                    break;
+                case 'n':
+                    c = '\n';
+                    break;
+                case 'r':
+                    c = '\r';
+                    break;
+                case 'u':
+                    c = unicode();
+                    current += 4;
+                    break;
+                default:
+                    // We just take the next character literally
+                    // but have the escaped flag set, important for {},[] etc
+            }
+        }
+        if (cursor > last && cursor <= current)
+        {
+            c0 = program != null ? program.size() : 0;
+            c1 = statements != null ? statements.size() : 0;
+            c2 = statement != null ? statement.size() : 0;
+            c3 = (start >= 0) ? current - start : 0;
+        }
+        if (!increment)
+        {
+            current = last;
+        }
+        return c;
+    }
+
+    public List<List<List<CharSequence>>> program()
+    {
+        program = new ArrayList<List<List<CharSequence>>>();
+        ws();
+        if (!eof())
+        {
+            program.add(pipeline());
+            while (peek() == ';')
+            {
+                current++;
+                List<List<CharSequence>> pipeline = pipeline();
+                program.add(pipeline);
+            }
+        }
+        if (!eof())
+        {
+            throw new RuntimeException("Program has trailing text: " + context(current));
+        }
+
+        List<List<List<CharSequence>>> p = program;
+        program = null;
+        return p;
+    }
+
+    CharSequence context(int around)
+    {
+        return text.subSequence(Math.max(0, current - 20), Math.min(text.length(),
+            current + 4));
+    }
+
+    public List<List<CharSequence>> pipeline()
+    {
+        statements = new ArrayList<List<CharSequence>>();
+        statements.add(statement());
+        while (peek() == '|')
+        {
+            current++;
+            ws();
+            if (!eof())
+            {
+                statements.add(statement());
+            }
+            else
+            {
+                statements.add(new ArrayList<CharSequence>());
+                break;
+            }
+        }
+        List<List<CharSequence>> s = statements;
+        statements = null;
+        return s;
+    }
+
+    public List<CharSequence> statement()
+    {
+        statement = new ArrayList<CharSequence>();
+        statement.add(value());
+        while (!eof())
+        {
+            ws();
+            if (peek() == '|' || peek() == ';')
+            {
+                break;
+            }
+
+            if (!eof())
+            {
+                statement.add(messy());
+            }
+        }
+        List<CharSequence> s = statement;
+        statement = null;
+        return s;
+    }
+
+    public CharSequence messy()
+    {
+        char c = peek();
+        if (c > 0 && SPECIAL.indexOf(c) < 0)
+        {
+            start = current++;
+            try {
+                while (!eof())
+                {
+                    c = peek();
+                    if (!escaped && (c == ';' || c == '|' || Character.isWhitespace(c)))
+                    {
+                        break;
+                    }
+                    next();
+                }
+                return text.subSequence(start, current);
+            } finally {
+                start = -1;
+            }
+        }
+        else
+        {
+            return value();
+        }
+    }
+
+    CharSequence value()
+    {
+        ws();
+
+        start = current;
+        try {
+            char c = next();
+            if (!escaped)
+            {
+                switch (c)
+                {
+                    case '{':
+                        return text.subSequence(start, find('}', '{'));
+                    case '(':
+                        return text.subSequence(start, find(')', '('));
+                    case '[':
+                        return text.subSequence(start, find(']', '['));
+                    case '<':
+                        return text.subSequence(start, find('>', '<'));
+                    case '=':
+                        return text.subSequence(start, current);
+                    case '"':
+                    case '\'':
+                        quote(c);
+                        break;
+                }
+            }
+
+            // Some identifier or number
+            while (!eof())
+            {
+                c = peek();
+                if (!escaped)
+                {
+                    if (Character.isWhitespace(c) || c == ';' || c == '|' || c == '=')
+                    {
+                        break;
+                    }
+                    else if (c == '{')
+                    {
+                        next();
+                        find('}', '{');
+                    }
+                    else if (c == '(')
+                    {
+                        next();
+                        find(')', '(');
+                    }
+                    else if (c == '<')
+                    {
+                        next();
+                        find('>', '<');
+                    }
+                    else if (c == '[')
+                    {
+                        next();
+                        find(']', '[');
+                    }
+                    else if (c == '\'' || c == '"')
+                    {
+                        next();
+                        quote(c);
+                        next();
+                    }
+                    else
+                    {
+                        next();
+                    }
+                }
+                else
+                {
+                    next();
+                }
+            }
+            return text.subSequence(start, current);
+        } finally {
+            start = -1;
+        }
+    }
+
+    boolean escaped()
+    {
+        return escaped;
+    }
+
+    char next()
+    {
+        return peek(true);
+    }
+
+    char unicode()
+    {
+        if (current + 4 > text.length())
+        {
+            throw new IllegalArgumentException("Unicode \\u escape at eof at pos ..."
+                + context(current) + "...");
+        }
+
+        String s = text.subSequence(current, current + 4).toString();
+        int n = Integer.parseInt(s, 16);
+        return (char) n;
+    }
+
+    int find(char target, char deeper)
+    {
+        int start = current;
+        int level = 1;
+
+        while (level != 0)
+        {
+            if (eof())
+            {
+                throw new RuntimeException("Eof found in the middle of a compound for '"
+                    + target + deeper + "', begins at " + context(start));
+            }
+
+            char c = next();
+            if (!escaped)
+            {
+                if (c == target)
+                {
+                    level--;
+                }
+                else
+                {
+                    if (c == deeper)
+                    {
+                        level++;
+                    }
+                    else
+                    {
+                        if (c == '"')
+                        {
+                            quote('"');
+                        }
+                        else
+                        {
+                            if (c == '\'')
+                            {
+                                quote('\'');
+                            }
+                            else
+                            {
+                                if (c == '`')
+                                {
+                                    quote('`');
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return current;
+    }
+
+    int quote(char which)
+    {
+        while (!eof() && (peek() != which || escaped))
+        {
+            next();
+        }
+
+        return current++;
+    }
+
+    CharSequence findVar()
+    {
+        int start = current;
+        char c = peek();
+
+        if (c == '{')
+        {
+            next();
+            int end = find('}', '{');
+            return text.subSequence(start, end);
+        }
+        if (c == '(')
+        {
+            next();
+            int end = find(')', '(');
+            return text.subSequence(start, end);
+        }
+
+        if (Character.isJavaIdentifierPart(c))
+        {
+            while (c == '$')
+            {
+                c = next();
+            }
+            while (!eof() && (Character.isJavaIdentifierPart(c) || c == '.') && c != '$')
+            {
+                next();
+                c = peek();
+            }
+            return text.subSequence(start, current);
+        }
+        throw new IllegalArgumentException(
+            "Reference to variable does not match syntax of a variable: "
+                + context(start));
+    }
+
+    public String toString()
+    {
+        return "..." + context(current) + "...";
+    }
+
+    public String unescape()
+    {
+        StringBuilder sb = new StringBuilder();
+        while (!eof())
+        {
+            sb.append(next());
+        }
+        return sb.toString();
+    }
+}

Modified: karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/jline/Console.java
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/jline/Console.java?rev=992437&r1=992436&r2=992437&view=diff
==============================================================================
--- karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/jline/Console.java (original)
+++ karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/jline/Console.java Fri Sep  3 19:51:43 2010
@@ -28,7 +28,6 @@ import java.io.InterruptedIOException;
 import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.io.Reader;
-import java.util.Arrays;
 import java.util.Map;
 import java.util.Properties;
 import java.util.concurrent.ArrayBlockingQueue;
@@ -42,9 +41,7 @@ import jline.Terminal;
 import jline.UnsupportedTerminal;
 import org.apache.karaf.shell.console.CloseShellException;
 import org.apache.karaf.shell.console.Completer;
-import org.apache.karaf.shell.console.completer.AggregateCompleter;
 import org.apache.karaf.shell.console.completer.CommandsCompleter;
-import org.apache.karaf.shell.console.completer.SessionScopeCompleter;
 import org.fusesource.jansi.Ansi;
 import org.osgi.service.command.CommandProcessor;
 import org.osgi.service.command.CommandSession;
@@ -242,13 +239,7 @@ public class Console implements Runnable
     }
 
     protected Completer createCompleter() {
-        Completer completer = new CommandsCompleter(session);
-        return new AggregateCompleter(
-                    Arrays.asList(
-                        completer,
-                        new SessionScopeCompleter( session, completer )
-                    )
-                );
+        return new CommandsCompleter(session);
     }
 
     protected Properties loadBrandingProperties() {

Added: karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/console/completer/ArgumentCompleterTest.java
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/console/completer/ArgumentCompleterTest.java?rev=992437&view=auto
==============================================================================
--- karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/console/completer/ArgumentCompleterTest.java (added)
+++ karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/console/completer/ArgumentCompleterTest.java Fri Sep  3 19:51:43 2010
@@ -0,0 +1,77 @@
+/**
+ * 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.karaf.shell.console.completer;
+
+import java.util.List;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class ArgumentCompleterTest {
+
+    @Test
+    public void testParser1() throws Exception {
+        Parser parser = new Parser("echo foo | cat bar ; ta", 23);
+        List<List<List<CharSequence>>> p = parser.program();
+        assertEquals(1, parser.c0);
+        assertEquals(0, parser.c1);
+        assertEquals(0, parser.c2);
+        assertEquals(2, parser.c3);
+    }
+
+    @Test
+    public void testParser2() throws Exception {
+        Parser parser = new Parser("echo foo ; cat bar | ta", 23);
+        List<List<List<CharSequence>>> p = parser.program();
+        assertEquals(1, parser.c0);
+        assertEquals(1, parser.c1);
+        assertEquals(0, parser.c2);
+        assertEquals(2, parser.c3);
+    }
+
+    @Test
+    public void testParser3() throws Exception {
+        Parser parser = new Parser("echo foo ; cat bar | ta", 22);
+        List<List<List<CharSequence>>> p = parser.program();
+        assertEquals(1, parser.c0);
+        assertEquals(1, parser.c1);
+        assertEquals(0, parser.c2);
+        assertEquals(1, parser.c3);
+    }
+
+    @Test
+    public void testParser4() throws Exception {
+        Parser parser = new Parser("echo foo ; cat bar | ta reta", 27);
+        List<List<List<CharSequence>>> p = parser.program();
+        assertEquals(1, parser.c0);
+        assertEquals(1, parser.c1);
+        assertEquals(1, parser.c2);
+        assertEquals(3, parser.c3);
+    }
+
+    @Test
+    public void testParser5() throws Exception {
+        Parser parser = new Parser("echo foo ; cat bar | ta reta", 24);
+        List<List<List<CharSequence>>> p = parser.program();
+        assertEquals(1, parser.c0);
+        assertEquals(1, parser.c1);
+        assertEquals(1, parser.c2);
+        assertEquals(0, parser.c3);
+    }
+
+}