Improve gogo parser to fully parse the command line instead of skipping inner constructs such as arrays or closures, and report missing terminators

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1735994 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/gogo/runtime/pom.xml b/gogo/runtime/pom.xml
index 23a686c..4ee7ff9 100644
--- a/gogo/runtime/pom.xml
+++ b/gogo/runtime/pom.xml
@@ -1,86 +1,86 @@
-<!-- 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. -->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-	<parent>
-		<groupId>org.apache.felix</groupId>
-		<artifactId>gogo-parent</artifactId>
-		<version>0.6.0</version>
-		<relativePath>../gogo-parent/pom.xml</relativePath>
-	</parent>
-	<modelVersion>4.0.0</modelVersion>
-	<packaging>bundle</packaging>
-	<name>Apache Felix Gogo Runtime</name>
-	<artifactId>org.apache.felix.gogo.runtime</artifactId>
-	<version>0.16.3-SNAPSHOT</version>
-	<dependencies>
-		<dependency>
-			<groupId>org.osgi</groupId>
-			<artifactId>org.osgi.core</artifactId>
-			<version>4.0.0</version>
-			<scope>provided</scope>
-		</dependency>
-		<dependency>
-			<groupId>org.osgi</groupId>
-			<artifactId>org.osgi.compendium</artifactId>
-			<version>4.0.0</version>
-			<scope>provided</scope>
-		</dependency>
-		<dependency>
-			<groupId>junit</groupId>
-			<artifactId>junit</artifactId>
-			<scope>test</scope>
-		</dependency>
-		<dependency>
-			<groupId>org.mockito</groupId>
-			<artifactId>mockito-core</artifactId>
-			<version>1.10.19</version>
-			<scope>test</scope>
-		</dependency>
-	</dependencies>
-	<build>
-		<plugins>
-			<plugin>
-				<groupId>org.apache.felix</groupId>
-				<artifactId>maven-bundle-plugin</artifactId>
-				<extensions>true</extensions>
-				<configuration>
-					<instructions>
-						<Export-Service>
-							org.apache.felix.service.threadio.ThreadIO,
-							org.apache.felix.service.command.CommandProcessor
-						</Export-Service>
-						<Export-Package>
-							org.apache.felix.service.command;
-							org.apache.felix.service.threadio; version=${project.version}; status="provisional";
-							mandatory:="status",
-							org.apache.felix.gogo.api; version=${project.version}
-						</Export-Package>
-						<Import-Package>
-							org.osgi.service.event*; resolution:=optional,
-							org.osgi.service.log*; resolution:=optional,
-							org.osgi.service.packageadmin*; resolution:=optional,
-							org.osgi.service.startlevel*; resolution:=optional,
-							*
-						</Import-Package>
-						<Private-Package>org.apache.felix.gogo.runtime*</Private-Package>
-						<Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
-						<Bundle-Vendor>The Apache Software Foundation</Bundle-Vendor>
-						<Bundle-Activator>org.apache.felix.gogo.runtime.activator.Activator</Bundle-Activator>
-						<Include-Resource>{maven-resources},META-INF/LICENSE=LICENSE,META-INF/NOTICE=NOTICE,META-INF/DEPENDENCIES=DEPENDENCIES</Include-Resource>
-						<_versionpolicy>[$(version;==;$(@)),$(version;+;$(@)))</_versionpolicy>
-						<_removeheaders>Private-Package,Ignore-Package,Include-Resource</_removeheaders>
-					</instructions>
-				</configuration>
-			</plugin>
-		</plugins>
-	</build>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>gogo-parent</artifactId>
+        <version>0.6.0</version>
+        <relativePath>../gogo-parent/pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <packaging>bundle</packaging>
+    <name>Apache Felix Gogo Runtime</name>
+    <artifactId>org.apache.felix.gogo.runtime</artifactId>
+    <version>0.16.3-SNAPSHOT</version>
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <version>4.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <version>4.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Export-Service>
+                            org.apache.felix.service.threadio.ThreadIO,
+                            org.apache.felix.service.command.CommandProcessor
+                        </Export-Service>
+                        <Export-Package>
+                            org.apache.felix.service.command;
+                            org.apache.felix.service.threadio; version=${project.version}; status="provisional"; mandatory:="status",
+                            org.apache.felix.gogo.api; version=${project.version}
+                        </Export-Package>
+                        <Import-Package>
+                            org.osgi.service.event*; resolution:=optional,
+                            org.osgi.service.log*; resolution:=optional,
+                            org.osgi.service.packageadmin*; resolution:=optional,
+                            org.osgi.service.startlevel*; resolution:=optional,
+                            *
+                        </Import-Package>
+                        <Private-Package>org.apache.felix.gogo.runtime*</Private-Package>
+                        <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
+                        <Bundle-Vendor>The Apache Software Foundation</Bundle-Vendor>
+                        <Bundle-Activator>org.apache.felix.gogo.runtime.activator.Activator</Bundle-Activator>
+                        <Include-Resource>{maven-resources},META-INF/LICENSE=LICENSE,META-INF/NOTICE=NOTICE,META-INF/DEPENDENCIES=DEPENDENCIES</Include-Resource>
+                        <_versionpolicy>[$(version;==;$(@)),$(version;+;$(@)))</_versionpolicy>
+                        <_removeheaders>Private-Package,Ignore-Package,Include-Resource</_removeheaders>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
 </project>
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/BaseTokenizer.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/BaseTokenizer.java
new file mode 100644
index 0000000..2ec6887
--- /dev/null
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/BaseTokenizer.java
@@ -0,0 +1,296 @@
+/*
+ * 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.runtime;
+
+public class BaseTokenizer
+{
+
+    protected static final char EOT = (char) -1;
+
+    protected final Token text;
+
+    protected short line;
+    protected short column;
+    protected char ch;
+    protected int index;
+
+    public BaseTokenizer(CharSequence text)
+    {
+        this.text = text instanceof Token ? (Token) text : new Token(text);
+        getch();
+    }
+
+    public Token text()
+    {
+        return text;
+    }
+
+    protected void find(char target, char deeper)
+    {
+        final short sLine = line;
+        final short sCol = column;
+        int start = ch;
+        int level = 1;
+
+        while (level != 0)
+        {
+            if (eot())
+            {
+                throw new EOFError(sLine, sCol, "unexpected eof found in the middle of a compound for '"
+                        + deeper + target + "', begins at " + start, "compound", Character.toString(target));
+                // TODO: fill context correctly
+            }
+
+            getch();
+            if (ch == '\\')
+            {
+                escape();
+            }
+            if (ch == target)
+            {
+                level--;
+            }
+            else
+            {
+                if (ch == deeper)
+                {
+                    level++;
+                }
+                else
+                {
+                    if (ch == '"' || ch == '\'' || ch == '`')
+                    {
+                        skipQuote();
+                    }
+                }
+            }
+        }
+    }
+
+    protected char escape()
+    {
+        assert '\\' == ch;
+        final short sLine = line;
+        final short sCol = column;
+
+        switch (getch())
+        {
+            case 'u':
+                getch();
+                int nb = 0;
+                for (int i = 0; i < 4; i++)
+                {
+                    char ch = Character.toUpperCase(this.ch);
+                    if (ch >= '0' && ch <= '9')
+                    {
+                        nb = nb * 16 + (ch - '0');
+                        getch();
+                        continue;
+                    }
+                    if (ch >= 'A' && ch <= 'F')
+                    {
+                        nb = nb * 16 + (ch - 'A' + 10);
+                        getch();
+                        continue;
+                    }
+                    if (ch == 0) {
+                        throw new EOFError(sLine, sCol, "unexpected EOT in \\ escape", "escape", "0");
+                    } else {
+                        throw new SyntaxError(sLine, sCol, "bad unicode", text);
+                    }
+                }
+                index--;
+                return (char) nb;
+
+            case EOT:
+                throw new EOFError(sLine, sCol, "unexpected EOT in \\ escape", "escape", " ");
+
+            case '\n':
+                return '\0'; // line continuation
+
+            case '\\':
+            case '\'':
+            case '"':
+            case '$':
+                return ch;
+
+            default:
+                return ch;
+        }
+    }
+
+    protected void skipQuote()
+    {
+        assert '\'' == ch || '"' == ch;
+        final char quote = ch;
+        final short sLine = line;
+        final short sCol = column;
+
+        while (getch() != EOT)
+        {
+            if (quote == ch)
+            {
+                return;
+            }
+
+            if ((quote == '"') && ('\\' == ch))
+                escape();
+        }
+
+        throw new EOFError(sLine, sCol, "unexpected EOT looking for matching quote: "
+                + quote,
+                quote == '"' ? "dquote" : "quote",
+                Character.toString(quote));
+    }
+
+    protected void skipSpace()
+    {
+        skipSpace(false);
+    }
+
+    protected void skipSpace(boolean skipNewLines)
+    {
+        while (true)
+        {
+            while (isBlank(ch))
+            {
+                getch();
+            }
+
+            // skip continuation lines, but not other escapes
+            if (('\\' == ch) && (peek() == '\n'))
+            {
+                getch();
+                getch();
+                continue;
+            }
+
+            if (skipNewLines && ('\n' == ch))
+            {
+                getch();
+                continue;
+            }
+
+            if (skipNewLines && ('\r' == ch) && (peek() == '\n'))
+            {
+                getch();
+                getch();
+                continue;
+            }
+
+            // skip comments
+            if (('/' == ch) || ('#' == ch))
+            {
+                if (('#' == ch) || (peek() == '/'))
+                {
+                    while ((getch() != EOT) && ('\n' != ch))
+                    {
+                    }
+                    continue;
+                }
+                else if ('*' == peek())
+                {
+                    short sLine = line;
+                    short sCol = column;
+                    getch();
+
+                    while ((getch() != EOT) && !(('*' == ch) && (peek() == '/')))
+                    {
+                    }
+
+                    if (EOT == ch)
+                    {
+                        throw new EOFError(sLine, sCol,
+                                "unexpected EOT looking for closing comment: */",
+                                "comment",
+                                "*/");
+                    }
+
+                    getch();
+                    getch();
+                    continue;
+                }
+            }
+
+            break;
+        }
+    }
+
+    protected boolean isBlank(char ch)
+    {
+        return ' ' == ch || '\t' == ch;
+    }
+
+    protected boolean eot()
+    {
+        return index >= text.length();
+    }
+
+    protected char getch()
+    {
+        return ch = getch(false);
+    }
+
+    protected char peek()
+    {
+        return getch(true);
+    }
+
+    protected char getch(boolean peek)
+    {
+        if (eot())
+        {
+            if (!peek)
+            {
+                ++index;
+                ch = EOT;
+            }
+            return EOT;
+        }
+
+        int current = index;
+        char c = text.charAt(index++);
+
+//        if (('\r' == c) && !eot() && (text.charAt(index) == '\n'))
+//            c = text.charAt(index++);
+
+        if (peek)
+        {
+            index = current;
+        }
+        else if ('\n' == c)
+        {
+            ++line;
+            column = 0;
+        }
+        else
+            ++column;
+
+        return c;
+    }
+
+    public void skip(int length)
+    {
+        while (--length >= 0)
+        {
+            getch();
+        }
+    }
+
+}
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Closure.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Closure.java
index 77db7e4..e945cdc 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Closure.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Closure.java
@@ -21,17 +21,24 @@
 import java.io.EOFException;
 import java.util.AbstractList;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 
-import org.apache.felix.gogo.runtime.Tokenizer.Type;
+import org.apache.felix.gogo.runtime.Parser.Array;
+import org.apache.felix.gogo.runtime.Parser.Executable;
+import org.apache.felix.gogo.runtime.Parser.Pipeline;
+import org.apache.felix.gogo.runtime.Parser.Program;
+import org.apache.felix.gogo.runtime.Parser.Sequence;
+import org.apache.felix.gogo.runtime.Parser.Statement;
 import org.apache.felix.service.command.CommandSession;
 import org.apache.felix.service.command.Function;
 
 public class Closure implements Function, Evaluate
 {
+
     public static final String LOCATION = ".location";
     private static final String DEFAULT_LOCK = ".defaultLock";
 
@@ -40,7 +47,7 @@
     private final CommandSessionImpl session;
     private final Closure parent;
     private final CharSequence source;
-    private final List<List<List<Token>>> program;
+    private final Program program;
     private final Object script;
 
     private Token errTok;
@@ -53,18 +60,34 @@
         this.session = session;
         this.parent = parent;
         this.source = source;
-        script = session.get("0"); // by convention, $0 is script name
+        this.script = session.get("0"); // by convention, $0 is script name
 
-        try
+        if (source instanceof Program)
         {
-            program = new Parser(source).program();
+            program = (Program) source;
         }
-        catch (Exception e)
+        else
         {
-            throw setLocation(e);
+            try
+            {
+                this.program = new Parser(source).program();
+            }
+            catch (Exception e)
+            {
+                throw setLocation(e);
+            }
         }
     }
 
+    public Closure(CommandSessionImpl session, Closure parent, Program program)
+    {
+        this.session = session;
+        this.parent = parent;
+        this.source = program;
+        this.script = session.get("0"); // by convention, $0 is script name
+        this.program = program;
+    }
+
     public CommandSessionImpl session()
     {
         return session;
@@ -115,7 +138,7 @@
         try
         {
             location.remove();
-            session.variables.remove(LOCATION);
+            session.put(LOCATION, null);
             return execute(values);
         }
         catch (Exception e)
@@ -152,15 +175,14 @@
         Pipe last = null;
         Object[] mark = Pipe.mark();
 
-        for (List<List<Token>> pipeline : program)
+        for (Executable executable : program.tokens())
         {
-            ArrayList<Pipe> pipes = new ArrayList<Pipe>();
+            List<Pipe> pipes = toPipes(executable);
 
-            for (List<Token> statement : pipeline)
+            for (int i = 0; i < pipes.size(); i++)
             {
-                Pipe current = new Pipe(this, statement);
-
-                if (pipes.isEmpty())
+                Pipe current = pipes.get(i);
+                if (i == 0)
                 {
                     if (current.out == null)
                     {
@@ -171,10 +193,9 @@
                 }
                 else
                 {
-                    Pipe previous = pipes.get(pipes.size() - 1);
+                    Pipe previous = pipes.get(i - 1);
                     previous.connect(current);
                 }
-                pipes.add(current);
             }
 
             if (pipes.size() == 1)
@@ -204,10 +225,9 @@
                 }
             }
 
-            last = pipes.remove(pipes.size() - 1);
-
-            for (Pipe pipe : pipes)
+            for (int i = 0; i < pipes.size() - 1; i++)
             {
+                Pipe pipe = pipes.get(i);
                 if (pipe.exception != null)
                 {
                     // can't throw exception, as result is defined by last pipe
@@ -218,7 +238,7 @@
                     session.put("pipe-exception", pipe.exception);
                 }
             }
-
+            last = pipes.get(pipes.size() - 1);
             if (last.exception != null)
             {
                 Pipe.reset(mark);
@@ -231,6 +251,24 @@
         return last == null ? null : last.result;
     }
 
+    private List<Pipe> toPipes(Executable executable)
+    {
+        if (executable instanceof Pipeline)
+        {
+            List<Pipe> pipes = new ArrayList<Pipe>();
+            Pipeline pipeline = (Pipeline) executable;
+            for (Executable ex : pipeline.tokens())
+            {
+                pipes.add(new Pipe(this, ex));
+            }
+            return pipes;
+        }
+        else
+        {
+            return Collections.singletonList(new Pipe(this, executable));
+        }
+    }
+
     private Object eval(Object v)
     {
         String s = v.toString();
@@ -256,6 +294,7 @@
             }
             catch (NumberFormatException e)
             {
+                // Ignore
             }
         }
         return v;
@@ -263,44 +302,43 @@
 
     public Object eval(final Token t) throws Exception
     {
-        Object v = null;
-
-        switch (t.type)
+        if (t instanceof Parser.Closure)
         {
-            case WORD:
-                v = Tokenizer.expand(t, this);
-
-                if (t == v)
-                {
-                    v = eval(v);
-                }
-                break;
-
-            case CLOSURE:
-                v = new Closure(session, this, t);
-                break;
-
-            case EXECUTION:
-                v = new Closure(session, this, t).execute(session, parms);
-                break;
-
-            case ARRAY:
-                v = array(t);
-                break;
-
-            case ASSIGN:
-                v = t.type;
-                break;
-
-            case EXPR:
-                v = expr(t.value);
-                break;
-
-            default:
-                throw new SyntaxError(t.line, t.column, "unexpected token: " + t.type);
+            return new Closure(session, this, ((Parser.Closure) t).program());
         }
+        else if (t instanceof Sequence)
+        {
+            return new Closure(session, this, ((Sequence) t).program())
+                    .execute(session, parms);
+        }
+        else if (t instanceof Array)
+        {
+            return array((Array) t);
+        }
+        else {
+            Object v = Expander.expand(t, this);
+            if (t == v)
+            {
+                v = eval(v);
+            }
+            return v;
+        }
+    }
 
-        return v;
+    public Object execute(Executable executable) throws Exception
+    {
+        if (executable instanceof Statement)
+        {
+            return executeStatement((Statement) executable);
+        }
+        else if (executable instanceof Sequence)
+        {
+            return new Closure(session, this, ((Sequence) executable).program()).execute(new ArrayList<Object>());
+        }
+        else
+        {
+            throw new IllegalStateException();
+        }
     }
 
     /*
@@ -311,7 +349,7 @@
      *    <object> // value of <object>
      *    <object> word.. // method call
      */
-    public Object executeStatement(List<Token> statement) throws Exception
+    public Object executeStatement(Statement statement) throws Exception
     {
         Object echo = session.get("echo");
         String xtrace = null;
@@ -319,32 +357,31 @@
         if (echo != null && !"false".equals(echo.toString()))
         {
             // set -x execution trace
-            StringBuilder buf = new StringBuilder("+");
-            for (Token token : statement)
-            {
-                buf.append(' ');
-                buf.append(token.source());
-            }
-            xtrace = buf.toString();
+            xtrace = "+" + statement;
             session.err.println(xtrace);
         }
 
-        List<Object> values = new ArrayList<Object>();
-        errTok = statement.get(0);
-
-        if ((statement.size() > 3) && Type.ASSIGN.equals(statement.get(1).type))
+        List<Token> tokens = statement.tokens();
+        if (tokens.isEmpty())
         {
-            errTok2 = statement.get(2);
+            return null;
         }
 
-        for (Token t : statement)
+        List<Object> values = new ArrayList<Object>();
+        errTok = tokens.get(0);
+
+        if ((tokens.size() > 3) && Token.eq("=", tokens.get(1)))
+        {
+            errTok2 = tokens.get(2);
+        }
+
+        for (Token t : tokens)
         {
             Object v = eval(t);
 
-            if ((Type.EXECUTION == t.type) && (statement.size() == 1))
-            {
-                return v;
-            }
+//            if ((Token.Type.EXECUTION == t.type) && (tokens.size() == 1)) {
+//                return v;
+//            }
 
             if (parms == v && parms != null)
             {
@@ -368,7 +405,7 @@
         }
 
         if (cmd instanceof CharSequence && values.size() > 0
-            && Type.ASSIGN.equals(values.get(0)))
+                && Token.eq("=", tokens.get(1)))
         {
             values.remove(0);
             String scmd = cmd.toString();
@@ -376,7 +413,7 @@
 
             if (values.size() == 0)
             {
-                return session.variables.remove(scmd);
+                return session.put(scmd, null);
             }
 
             if (values.size() == 1)
@@ -389,12 +426,12 @@
                 if (null == cmd)
                 {
                     throw new RuntimeException("Command name evaluates to null: "
-                        + errTok2);
+                            + errTok2);
                 }
 
                 trace2(xtrace, cmd, values);
 
-                value = bareword(statement.get(2), cmd) ? executeCmd(cmd.toString(), values)
+                value = bareword(tokens.get(2), cmd) ? executeCmd(cmd.toString(), values)
                     : executeMethod(cmd, values);
             }
 
@@ -403,7 +440,7 @@
 
         trace2(xtrace, cmd, values);
 
-        return bareword(statement.get(0), cmd) ? executeCmd(cmd.toString(), values)
+        return bareword(tokens.get(0), cmd) ? executeCmd(cmd.toString(), values)
             : executeMethod(cmd, values);
     }
 
@@ -431,7 +468,7 @@
 
     private boolean bareword(Token t, Object v) throws Exception
     {
-        return ((t.type == Type.WORD) && t.value.toString().equals(v));
+        return v instanceof CharSequence && Token.eq(t, (CharSequence) v);
     }
 
     private Object executeCmd(String scmd, List<Object> values) throws Exception
@@ -469,7 +506,7 @@
                         }
                         finally
                         {
-                            session.variables.remove(DEFAULT_LOCK);
+                            session.put(DEFAULT_LOCK, null);
                         }
                     }
                 }
@@ -533,22 +570,21 @@
 
     private Object assignment(String name, Object value)
     {
-        session.variables.put(name, value);
+        session.put(name, value);
         return value;
     }
 
-    private Object expr(CharSequence expr) throws Exception
+    public Object expr(Token expr)
     {
         return session.expr(expr);
     }
 
-    private Object array(Token array) throws Exception
+    private Object array(Array array) throws Exception
     {
-        List<Token> list = new ArrayList<Token>();
-        Map<Token, Token> map = new LinkedHashMap<Token, Token>();
-        (new Parser(array)).array(list, map);
+        List<Token> list = array.list();
+        Map<Token, Token> map = array.map();
 
-        if (map.isEmpty())
+        if (list != null)
         {
             List<Object> olist = new ArrayList<Object>();
             for (Token t : list)
@@ -556,10 +592,7 @@
                 Object oval = eval(t);
                 if (oval.getClass().isArray())
                 {
-                    for (Object o : (Object[]) oval)
-                    {
-                        olist.add(o);
-                    }
+                    Collections.addAll(olist, (Object[]) oval);
                 }
                 else
                 {
@@ -577,7 +610,7 @@
                 Object k = eval(key);
                 if (!(k instanceof String))
                 {
-                    throw new SyntaxError(key.line, key.column,
+                    throw new SyntaxError(key.line(), key.column(),
                         "map key null or not String: " + key);
                 }
                 omap.put(k, eval(e.getValue()));
@@ -620,14 +653,14 @@
 
     public Object put(String key, Object value)
     {
-        return session.variables.put(key, value);
+        return session.put(key, value);
     }
 
     @Override
     public String toString()
     {
         return source.toString().trim().replaceAll("\n+", "\n").replaceAll(
-            "([^\\\\{(\\[])\n", "\\1;").replaceAll("[ \\\\\t\n]+", " ");
+            "([^\\\\{}(\\[])[\\s\n]*\n", "$1;").replaceAll("[ \\\\\t\n]+", " ");
     }
 
     /**
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandProcessorImpl.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandProcessorImpl.java
index 7f987dc..c8fcd3a 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandProcessorImpl.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandProcessorImpl.java
@@ -36,6 +36,7 @@
 import org.apache.felix.service.command.CommandProcessor;
 import org.apache.felix.service.command.CommandSession;
 import org.apache.felix.service.command.Converter;
+import org.apache.felix.service.command.Descriptor;
 import org.apache.felix.service.command.Function;
 import org.apache.felix.service.threadio.ThreadIO;
 
@@ -68,7 +69,7 @@
         }
     }
 
-    void removeSession(CommandSessionImpl session)
+    void closeSession(CommandSessionImpl session)
     {
         synchronized (sessions)
         {
@@ -179,13 +180,15 @@
         return new CommandProxy(cmd, cfunction.substring(1));
     }
 
-    public void addCommand(String scope, Object target)
+    @Descriptor("add commands")
+    public void addCommand(@Descriptor("scope") String scope, @Descriptor("target") Object target)
     {
         Class<?> tc = (target instanceof Class<?>) ? (Class<?>) target : target.getClass();
         addCommand(scope, target, tc);
     }
 
-    public void addCommand(String scope, Object target, Class<?> functions)
+    @Descriptor("add commands")
+    public void addCommand(@Descriptor("scope") String scope, @Descriptor("target") Object target, @Descriptor("functions") Class<?> functions)
     {
         addCommand(scope, target, functions, 0);
     }
@@ -283,7 +286,18 @@
         return functions;
     }
 
-    public Object convert(Class<?> desiredType, Object in)
+    public Object convert(CommandSession session, Class<?> desiredType, Object in)
+    {
+        int[] cost = new int[1];
+        Object ret = Reflective.coerce(session, desiredType, in, cost);
+        if (ret == Reflective.NO_MATCH) {
+            throw new IllegalArgumentException(String.format(
+                    "Cannot convert %s(%s) to %s", in, in != null ? in.getClass() : "null", desiredType));
+        }
+        return ret;
+    }
+
+    Object doConvert(Class<?> desiredType, Object in)
     {
         for (Converter c : converters)
         {
@@ -297,9 +311,11 @@
             }
             catch (Exception e)
             {
-                e.printStackTrace();
+                // Ignore
+                e.getCause();
             }
         }
+
         return null;
     }
 
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandSessionImpl.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandSessionImpl.java
index c1051ef..97ed681 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandSessionImpl.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandSessionImpl.java
@@ -24,6 +24,7 @@
 import java.io.InputStream;
 import java.io.PrintStream;
 import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -32,7 +33,10 @@
 import java.util.Formatter;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 
+import org.apache.felix.service.command.CommandProcessor;
 import org.apache.felix.service.command.CommandSession;
 import org.apache.felix.service.command.Converter;
 import org.apache.felix.service.command.Function;
@@ -43,14 +47,15 @@
     public static final String SESSION_CLOSED = "session is closed";
     public static final String VARIABLES = ".variables";
     public static final String COMMANDS = ".commands";
+    public static final String CONSTANTS = ".constants";
     private static final String COLUMN = "%-20s %s\n";
 
     protected InputStream in;
     protected PrintStream out;
-    protected PrintStream err;
+    PrintStream err;
 
     private final CommandProcessorImpl processor;
-    protected final Map<String, Object> variables = new HashMap<String, Object>();
+    protected final ConcurrentMap<String, Object> variables = new ConcurrentHashMap<String, Object>();
     private volatile boolean closed;
 
     protected CommandSessionImpl(CommandProcessorImpl shell, InputStream in, PrintStream out, PrintStream err)
@@ -66,11 +71,21 @@
         return processor.threadIO;
     }
 
+    public CommandProcessor processor()
+    {
+        return processor;
+    }
+
+    public ConcurrentMap<String, Object> getVariables()
+    {
+        return variables;
+    }
+
     public void close()
     {
         if (!this.closed)
         {
-            this.processor.removeSession(this);
+            this.processor.closeSession(this);
             this.closed = true;
         }
     }
@@ -119,6 +134,11 @@
             return processor.getCommands();
         }
 
+        if (CONSTANTS.equals(name))
+        {
+            return Collections.unmodifiableSet(processor.constants.keySet());
+        }
+
         Object val = processor.constants.get(name);
         if (val != null)
         {
@@ -152,11 +172,15 @@
         return processor.getCommand(name, variables.get("SCOPE"));
     }
 
-    public void put(String name, Object value)
+    public Object put(String name, Object value)
     {
-        synchronized (variables)
+        if (value != null)
         {
-            variables.put(name, value);
+            return variables.put(name, value);
+        }
+        else
+        {
+            return variables.remove(name);
         }
     }
 
@@ -343,52 +367,48 @@
     {
         boolean found = false;
         Formatter f = new Formatter();
-
-        try
+        Method methods[] = b.getClass().getMethods();
+        for (Method m : methods)
         {
-            Method methods[] = b.getClass().getMethods();
-            for (Method m : methods)
+            try
             {
-                try
+                String name = m.getName();
+                if (m.getName().startsWith("get") && !m.getName().equals("getClass") && m.getParameterTypes().length == 0 && Modifier.isPublic(m.getModifiers()))
                 {
-                    String name = m.getName();
-                    if (!name.equals("getClass") && name.startsWith("get") && m.getParameterTypes().length == 0)
-                    {
-                        m.setAccessible(true);
-                        Object value = m.invoke(b);
-
-                        found = true;
-                        name = name.substring(3);
-                        f.format(COLUMN, name, format(value, Converter.LINE, this));
-                    }
-                }
-                catch (IllegalAccessException e)
-                {
-                    // Ignore
-                }
-                catch (Exception e)
-                {
-                    e.printStackTrace();
+                    found = true;
+                    name = name.substring(3);
+                    m.setAccessible(true);
+                    Object value = m.invoke(b, (Object[]) null);
+                    f.format(COLUMN, name, format(value, Converter.LINE, this));
                 }
             }
-            if (found)
+            catch (IllegalAccessException e)
             {
-                return (StringBuilder) f.out();
+                // Ignore
             }
-            else
+            catch (Exception e)
             {
-                return b.toString();
+                e.printStackTrace();
             }
         }
-        finally
+        if (found)
         {
-            f.close();
+            return (StringBuilder) f.out();
+        }
+        else
+        {
+            return b.toString();
         }
     }
 
     public Object convert(Class<?> desiredType, Object in)
     {
-        return processor.convert(desiredType, in);
+        return processor.convert(this, desiredType, in);
+    }
+
+    public Object doConvert(Class<?> desiredType, Object in)
+    {
+        return processor.doConvert(desiredType, in);
     }
 
     public CharSequence format(Object result, int inspect)
@@ -399,7 +419,7 @@
         }
         catch (Exception e)
         {
-            return "<can not format " + result + ">:" + e;
+            return "<can not format " + result + ":" + e;
         }
     }
 
@@ -407,4 +427,5 @@
     {
         return processor.expr(this, expr);
     }
+
 }
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/EOFError.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/EOFError.java
index 81e57f4..b4d172d 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/EOFError.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/EOFError.java
@@ -21,9 +21,24 @@
 public class EOFError extends SyntaxError
 {
     private static final long serialVersionUID = 1L;
-    
-    public EOFError(int line, int column, String message)
+
+    private final String missing;
+    private final String repair;
+
+    public EOFError(int line, int column, String message, String missing, String repair)
     {
         super(line, column, message);
+        this.missing = missing;
+        this.repair = repair;
     }
+
+    public String repair() {
+        return repair;
+    }
+
+    public String missing()
+    {
+        return missing;
+    }
+
 }
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Evaluate.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Evaluate.java
index 1a54328..a015a8b 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Evaluate.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Evaluate.java
@@ -25,4 +25,6 @@
     Object get(String key);
     
     Object put(String key, Object value);
+
+    Object expr(Token t);
 }
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Expander.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Expander.java
new file mode 100644
index 0000000..08a233f
--- /dev/null
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Expander.java
@@ -0,0 +1,385 @@
+/*
+ * 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.runtime;
+
+@SuppressWarnings("fallthrough")
+public class Expander extends BaseTokenizer
+{
+
+    /**
+     * expand variables, quotes and escapes in word.
+     */
+    public static Object expand(CharSequence word, Evaluate eval) throws Exception
+    {
+        return expand(word, eval, false);
+    }
+
+    private static Object expand(CharSequence word, Evaluate eval, boolean inQuote) throws Exception
+    {
+        return new Expander(word, eval, inQuote).expand();
+    }
+
+    private final Evaluate evaluate;
+    private final boolean inQuote;
+
+    public Expander(CharSequence text, Evaluate evaluate, boolean inQuote)
+    {
+        super(text);
+        this.evaluate = evaluate;
+        this.inQuote = inQuote;
+    }
+
+    public Object expand(CharSequence word) throws Exception
+    {
+        return expand(word, evaluate, inQuote);
+    }
+
+    private Object expand() throws Exception
+    {
+        final String special = "%$\\\"'";
+        int i = text.length();
+        while ((--i >= 0) && (special.indexOf(text.charAt(i)) == -1));
+        // shortcut if word doesn't contain any special characters
+        if (i < 0)
+            return text;
+
+        StringBuilder buf = new StringBuilder();
+        Token value;
+
+        while (ch != EOT)
+        {
+            int start = index;
+
+            switch (ch)
+            {
+                case '%':
+                    Object exp = expandExp();
+
+                    if (EOT == ch && buf.length() == 0)
+                    {
+                        return exp;
+                    }
+
+                    if (null != exp)
+                    {
+                        buf.append(exp);
+                    }
+
+                    continue; // expandVar() has already read next char
+
+                case '$':
+                    Object val = expandVar();
+
+                    if (EOT == ch && buf.length() == 0)
+                    {
+                        return val;
+                    }
+
+                    if (null != val)
+                    {
+                        buf.append(val);
+                    }
+
+                    continue; // expandVar() has already read next char
+
+                case '\\':
+                    ch = (inQuote && ("u$\\\n\"".indexOf(peek()) == -1)) ? '\\'
+                            : escape();
+
+                    if (ch != '\0') // ignore line continuation
+                    {
+                        buf.append(ch);
+                    }
+
+                    break;
+
+                case '"':
+                    skipQuote();
+                    value = text.subSequence(start, index - 1);
+                    Object expand = expand(value, evaluate, true);
+                    if (eot() && buf.length() == 0 && value == expand)
+                    {
+                        // FELIX-2468 avoid returning CharSequence implementation
+                        return value.toString();
+                    }
+                    if (null != expand)
+                    {
+                        buf.append(expand.toString());
+                    }
+                    break;
+
+                case '\'':
+                    if (!inQuote)
+                    {
+                        skipQuote();
+                        value = text.subSequence(start, index - 1);
+
+                        if (eot() && buf.length() == 0)
+                        {
+                            return value.toString();
+                        }
+
+                        buf.append(value);
+                        break;
+                    }
+                    // else fall through
+                default:
+                    buf.append(ch);
+            }
+
+            getch();
+        }
+
+        return buf.toString();
+    }
+
+    private Object expandExp()
+    {
+        assert '%' == ch;
+        Object val;
+
+        if (getch() == '(')
+        {
+            val = evaluate.expr(group());
+            getch();
+            return val;
+        }
+        else
+        {
+            throw new SyntaxError(line, column, "bad expression: " + text);
+        }
+    }
+
+    private Token group()
+    {
+        final char push = ch;
+        final char pop;
+
+        switch (ch)
+        {
+            case '{':
+                pop = '}';
+                break;
+            case '(':
+                pop = ')';
+                break;
+            case '[':
+                pop = ']';
+                break;
+            default:
+                assert false;
+                pop = 0;
+        }
+
+        short sLine = line;
+        short sCol = column;
+        int start = index;
+        int depth = 1;
+
+        while (true)
+        {
+            boolean comment = false;
+
+            switch (ch)
+            {
+                case '{':
+                case '(':
+                case '[':
+                case '\n':
+                    comment = true;
+                    break;
+            }
+
+            if (getch() == EOT)
+            {
+                throw new EOFError(sLine, sCol, "unexpected EOT looking for matching '"
+                        + pop + "'", "compound", Character.toString(pop));
+            }
+
+            // don't recognize comments that start within a word
+            if (comment || isBlank(ch))
+                skipSpace();
+
+            switch (ch)
+            {
+                case '"':
+                case '\'':
+                    skipQuote();
+                    break;
+
+                case '\\':
+                    ch = escape();
+                    break;
+
+                default:
+                    if (push == ch)
+                        depth++;
+                    else if (pop == ch && --depth == 0)
+                        return text.subSequence(start, index - 1);
+            }
+        }
+
+    }
+
+    private Object expandVar() throws Exception
+    {
+        assert '$' == ch;
+        Object val;
+
+        if (getch() != '{')
+        {
+            if ('(' == ch)
+            { // support $(...) FELIX-2433
+                int start = index - 1;
+                find(')', '(');
+                Token p = text.subSequence(start, index);
+                val = evaluate.eval(new Parser(p).sequence());
+                getch();
+            }
+            else
+            {
+                int start = index - 1;
+                while (isName(ch))
+                {
+                    getch();
+                }
+
+                if (index - 1 == start)
+                {
+                    val = "$";
+                }
+                else
+                {
+                    String name = text.subSequence(start, index - 1).toString();
+                    val = evaluate.get(name);
+                }
+            }
+        }
+        else
+        {
+            // ${NAME[[:]-+=?]WORD}
+            short sLine = line;
+            short sCol = column;
+            Token group = group();
+            char c;
+            int i = 0;
+
+            while (i < group.length())
+            {
+                switch (group.charAt(i))
+                {
+                    case ':':
+                    case '-':
+                    case '+':
+                    case '=':
+                    case '?':
+                        break;
+
+                    default:
+                        ++i;
+                        continue;
+                }
+                break;
+            }
+
+            sCol += i;
+
+            String name = String.valueOf(expand(group.subSequence(0, i)));
+
+            for (int j = 0; j < name.length(); ++j)
+            {
+                if (!isName(name.charAt(j)))
+                {
+                    throw new SyntaxError(sLine, sCol, "bad name: ${" + group + "}");
+                }
+            }
+
+            val = evaluate.get(name);
+
+            if (i < group.length())
+            {
+                c = group.charAt(i++);
+                if (':' == c)
+                {
+                    c = (i < group.length() ? group.charAt(i++) : EOT);
+                }
+
+                Token word = group.subSequence(i, group.length());
+
+                switch (c)
+                {
+                    case '-':
+                    case '=':
+                        if (null == val)
+                        {
+                            val = expand(word, evaluate, false);
+                            if (val instanceof Token)
+                            {
+                                val = val.toString();
+                            }
+                            if ('=' == c)
+                            {
+                                evaluate.put(name, val);
+                            }
+                        }
+                        break;
+
+                    case '+':
+                        if (null != val)
+                        {
+                            val = expand(word, evaluate, false);
+                            if (val instanceof Token)
+                            {
+                                val = val.toString();
+                            }
+                        }
+                        break;
+
+                    case '?':
+                        if (null == val)
+                        {
+                            val = expand(word, evaluate, false);
+                            if (val instanceof Token)
+                            {
+                                val = val.toString();
+                            }
+                            if (null == val || val.toString().length() == 0)
+                            {
+                                val = "parameter not set";
+                            }
+                            throw new IllegalArgumentException(name + ": " + val);
+                        }
+                        break;
+
+                    default:
+                        throw new SyntaxError(sLine, sCol, "bad substitution: ${" + group + "}");
+                }
+            }
+            getch();
+        }
+
+        return val;
+    }
+
+    private boolean isName(char ch)
+    {
+        return Character.isJavaIdentifierPart(ch) && (ch != '$') || ('.' == ch);
+    }
+
+}
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Parser.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Parser.java
index fce18d6..3b9192c 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Parser.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Parser.java
@@ -16,167 +16,516 @@
  * 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.felix.gogo.runtime;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
-import org.apache.felix.gogo.runtime.Tokenizer.Type;
 
 public class Parser
 {
-    private final Tokenizer tz;
 
-    public Parser(CharSequence program)
+    public static abstract class Executable extends Token
     {
-        tz = new Tokenizer(program);
+        public Executable(Token cs)
+        {
+            super(cs);
+        }
     }
 
-    public List<List<List<Token>>> program()
+    public static class Statement extends Executable
     {
-        List<List<List<Token>>> program = new ArrayList<List<List<Token>>>();
+        private final List<Token> tokens;
 
-        outer: while (tz.next() != Type.EOT)
+        public Statement(Token cs, List<Token> tokens)
         {
-            program.add(pipeline());
+            super(cs);
+            this.tokens = tokens;
+        }
 
-            switch (tz.type())
-            {
-                case SEMICOLON:
-                case NEWLINE:
-                    continue;
+        public List<Token> tokens()
+        {
+            return tokens;
+        }
+    }
 
-                default:
-                    break outer;
+    /**
+     * pipe1 ; pipe2 ; ...
+     */
+    public static class Program extends Token
+    {
+        private final List<Executable> tokens;
+
+        public Program(Token cs, List<Executable> tokens)
+        {
+            super(cs);
+            this.tokens = tokens;
+        }
+
+        public List<Executable> tokens()
+        {
+            return tokens;
+        }
+    }
+
+    /**
+     * token1 | token2 | ...
+     */
+    public static class Pipeline extends Executable
+    {
+        private final List<Executable> tokens;
+
+        public Pipeline(Token cs, List<Executable> tokens)
+        {
+            super(cs);
+            this.tokens = tokens;
+        }
+
+        public List<Executable> tokens()
+        {
+            return tokens;
+        }
+
+    }
+
+    /**
+     * ( program )
+     */
+    public static class Sequence extends Executable
+    {
+        private final Program program;
+
+        public Sequence(Token cs, Program program)
+        {
+            super(cs);
+            this.program = program;
+        }
+
+        public Program program()
+        {
+            return program;
+        }
+    }
+
+    /**
+     * { program }
+     */
+    public static class Closure extends Token
+    {
+        private final Program program;
+
+        public Closure(Token cs, Program program)
+        {
+            super(cs);
+            this.program = program;
+        }
+
+        public Program program()
+        {
+            return program;
+        }
+    }
+
+    /**
+     * [ a b ...]
+     * [ k1=v1 k2=v2 ...]
+     */
+    public static class Array extends Token
+    {
+        private final List<Token> list;
+        private final Map<Token, Token> map;
+
+        public Array(Token cs, List<Token> list, Map<Token, Token> map)
+        {
+            super(cs);
+            assert list != null ^ map != null;
+            this.list = list;
+            this.map = map;
+        }
+
+        public List<Token> list()
+        {
+            return list;
+        }
+
+        public Map<Token, Token> map()
+        {
+            return map;
+        }
+    }
+
+    protected final Tokenizer tz;
+    protected final LinkedList<String> stack = new LinkedList<String>();
+    protected final List<Token> tokens = new ArrayList<Token>();
+    protected final List<Statement> statements = new ArrayList<Statement>();
+
+    public Parser(CharSequence line)
+    {
+        this.tz = new Tokenizer(line);
+    }
+
+    public List<Token> tokens() {
+        return Collections.unmodifiableList(tokens);
+    }
+
+    public List<Statement> statements() {
+        Collections.sort(statements, new Comparator<Statement>() {
+            public int compare(Statement o1, Statement o2) {
+                int x = o1.start();
+                int y = o2.start();
+                return (x < y) ? -1 : ((x == y) ? 0 : 1);
             }
-        }
-
-        if (tz.next() != Type.EOT)
-        {
-            throw new RuntimeException("Program has trailing text: " + tz.value());
-        }
-        return program;
+        });
+        return Collections.unmodifiableList(statements);
     }
 
-    private List<List<Token>> pipeline()
+    public Program program()
     {
-        List<List<Token>> pipeline = new ArrayList<List<Token>>();
-
+        List<Executable> tokens = new ArrayList<Executable>();
+        List<Executable> pipes = null;
+        int start = tz.index - 1;
         while (true)
         {
-            pipeline.add(command());
-            switch (tz.type())
+            Executable ex;
+            Token t = next();
+            if (t == null)
             {
-                case PIPE:
-                    if (tz.next() == Type.EOT)
-                    {
-                        Token t = tz.token();
-                        throw new EOFError(t.line, t.column, "unexpected EOT after pipe '|'");
-                    }
-                    break;
-
-                default:
-                    return pipeline;
-            }
-        }
-    }
-
-    private List<Token> command()
-    {
-        List<Token> command = new ArrayList<Token>();
-
-        while (true)
-        {
-            Token t = tz.token();
-
-            switch (t.type)
-            {
-                case WORD:
-                case CLOSURE:
-                case EXECUTION:
-                case ARRAY:
-                case ASSIGN:
-                case EXPR:
-                    break;
-
-                default:
-                    throw new SyntaxError(t.line, t.column, "unexpected token: " + t.type);
-            }
-
-            command.add(t);
-
-            switch (tz.next())
-            {
-                case PIPE:
-                case SEMICOLON:
-                case NEWLINE:
-                case EOT:
-                    return command;
-
-                default:
-                    break;
-            }
-        }
-    }
-
-    public void array(List<Token> list, Map<Token, Token> map) throws Exception
-    {
-        Token lt = null;
-        boolean isMap = false;
-
-        while (tz.next() != Type.EOT)
-        {
-            if (isMap)
-            {
-                Token key = lt;
-                lt = null;
-                if (null == key)
+                if (pipes != null)
                 {
-                    key = tz.token();
-
-                    if (tz.next() != Type.ASSIGN)
-                    {
-                        Token t = tz.token();
-                        throw new SyntaxError(t.line, t.column,
-                            "map expected '=', found: " + t);
-                    }
-
-                    tz.next();
+                    throw new EOFError(tz.line, tz.column, "unexpected EOT while looking for a statement after |", getMissing("pipe"), "0");
                 }
-
-                Token k = (list.isEmpty() ? key : list.remove(0));
-                Token v = tz.token();
-                map.put(k, v);
+                else
+                {
+                    return new Program(whole(tokens, start), tokens);
+                }
+            }
+            if (Token.eq("}", t) || Token.eq(")", t))
+            {
+                if (pipes != null)
+                {
+                    throw new EOFError(t.line, t.column, "unexpected token '" + t + "' while looking for a statement after |", getMissing("pipe"), "0");
+                }
+                else
+                {
+                    push(t);
+                    return new Program(whole(tokens, start), tokens);
+                }
             }
             else
             {
-                switch (tz.type())
+                push(t);
+                ex = statement();
+            }
+            t = next();
+            if (t == null || Token.eq(";", t) || Token.eq("\n", t))
+            {
+                if (pipes != null)
                 {
-                    case WORD:
-                    case CLOSURE:
-                    case EXECUTION:
-                    case ARRAY:
-                        lt = tz.token();
-                        list.add(lt);
-                        break;
-
-                    case ASSIGN:
-                        if (list.size() == 1)
-                        {
-                            isMap = true;
-                            break;
-                        }
-                        // fall through
-                    default:
-                        lt = tz.token();
-                        throw new SyntaxError(lt.line, lt.column,
-                            "unexpected token in list: " + lt);
+                    pipes.add(ex);
+                    tokens.add(new Pipeline(whole(pipes, start), pipes));
+                    pipes = null;
+                }
+                else
+                {
+                    tokens.add(ex);
+                }
+                if (t == null)
+                {
+                    return new Program(whole(tokens, start), tokens);
                 }
             }
+            else if (Token.eq("|", t))
+            {
+                if (pipes == null)
+                {
+                    pipes = new ArrayList<Executable>();
+                }
+                pipes.add(ex);
+            }
+            else
+            {
+                if (pipes != null)
+                {
+                    pipes.add(ex);
+                    tokens.add(new Pipeline(whole(pipes, start), pipes));
+                    pipes = null;
+                }
+                else
+                {
+                    tokens.add(ex);
+                }
+                push(t);
+            }
         }
     }
 
+    protected void push(Token t) {
+        tz.push(t);
+    }
+
+    protected Token next() {
+        boolean pushed = tz.pushed != null;
+        Token token = tz.next();
+        if (!pushed && token != null) {
+            tokens.add(token);
+        }
+        return token;
+    }
+
+    public Sequence sequence()
+    {
+        Token start = start("(", "sequence");
+        expectNotNull();
+        Program program = program();
+        Token end = end(")");
+        return new Sequence(whole(start, end), program);
+    }
+
+    public Closure closure()
+    {
+        Token start = start("{", "closure");
+        expectNotNull();
+        Program program = program();
+        Token end = end("}");
+        return new Closure(whole(start, end), program);
+    }
+
+    public Statement statement()
+    {
+        List<Token> tokens = new ArrayList<Token>();
+        int start = tz.index;
+        while (true)
+        {
+            Token t = next();
+            if (t == null
+                    || Token.eq("|", t)
+                    || Token.eq("\n", t)
+                    || Token.eq(";", t)
+                    || Token.eq("}", t)
+                    || Token.eq(")", t)
+                    || Token.eq("]", t))
+            {
+                push(t);
+                break;
+            }
+            if (Token.eq("{", t))
+            {
+                push(t);
+                tokens.add(closure());
+            }
+            else if (Token.eq("[", t))
+            {
+                push(t);
+                tokens.add(array());
+            }
+            else if (Token.eq("(", t))
+            {
+                push(t);
+                tokens.add(sequence());
+            }
+            else
+            {
+                tokens.add(t);
+            }
+        }
+        Statement statement = new Statement(whole(tokens, start), tokens);
+        statements.add(statement);
+        return statement;
+    }
+
+    public Array array()
+    {
+        Token start = start("[", "array");
+        Boolean isMap = null;
+        List<Token> list = new ArrayList<Token>();
+        Map<Token, Token> map = new LinkedHashMap<Token, Token>();
+        while (true)
+        {
+            Token key = next();
+            if (key == null)
+            {
+                throw new EOFError(tz.line, tz.column, "unexpected EOT", getMissing(), "]");
+            }
+            if (Token.eq("]", key))
+            {
+                push(key);
+                break;
+            }
+            if (Token.eq("\n", key))
+            {
+                continue;
+            }
+            if (Token.eq("{", key) || Token.eq(";", key)
+                    || Token.eq("|", key) || Token.eq(")", key) || Token.eq("}", key) || Token.eq("=", key))
+            {
+                throw new SyntaxError(key.line(), key.column(), "unexpected token '" + key + "' while looking for array key");
+            }
+            if (Token.eq("(", key))
+            {
+                push(key);
+                key = sequence();
+            }
+            if (Token.eq("[", key))
+            {
+                push(key);
+                key = array();
+            }
+            if (isMap == null)
+            {
+                Token n = next();
+                if (n == null)
+                {
+                    throw new EOFError(tz.line, tz.column, "unexpected EOF while looking for array token", getMissing(), "]");
+                }
+                isMap = Token.eq("=", n);
+                push(n);
+            }
+            if (isMap)
+            {
+                expect("=");
+                Token val = next();
+                if (val == null)
+                {
+                    throw new EOFError(tz.line, tz.column, "unexpected EOF while looking for array value", getMissing(), "0");
+                }
+                else if (Token.eq(";", val) || Token.eq("|", val)
+                        || Token.eq(")", key) || Token.eq("}", key) || Token.eq("=", key))
+                {
+                    throw new SyntaxError(key.line(), key.column(), "unexpected token '" + key + "' while looking for array value");
+                }
+                else if (Token.eq("[", val))
+                {
+                    push(val);
+                    val = array();
+                }
+                else if (Token.eq("(", val))
+                {
+                    push(val);
+                    val = sequence();
+                }
+                else if (Token.eq("{", val))
+                {
+                    push(val);
+                    val = closure();
+                }
+                map.put(key, val);
+            }
+            else
+            {
+                list.add(key);
+            }
+        }
+        Token end = end("]");
+        if (isMap == null || !isMap)
+        {
+            return new Array(whole(start, end), list, null);
+        }
+        else
+        {
+            return new Array(whole(start, end), null, map);
+        }
+    }
+
+    protected void expectNotNull()
+    {
+        Token t = next();
+        if (t == null)
+        {
+            throw new EOFError(tz.line, tz.column,
+                    "unexpected EOT",
+                    getMissing(), "0");
+        }
+        push(t);
+    }
+
+    private String getMissing() {
+        return getMissing(null);
+    }
+
+    private String getMissing(String additional) {
+        StringBuilder sb = new StringBuilder();
+        LinkedList<String> stack = this.stack;
+        if (additional != null) {
+            stack = new LinkedList<String>(stack);
+            stack.addLast(additional);
+        }
+        String last = null;
+        int nb = 0;
+        for (String cur : stack) {
+            if (last == null) {
+                last = cur;
+                nb = 1;
+            } else if (last.equals(cur)) {
+                nb++;
+            } else {
+                if (sb.length() > 0) {
+                    sb.append(" ");
+                }
+                sb.append(last);
+                if (nb > 1) {
+                    sb.append("(").append(nb).append(")");
+                }
+                last = cur;
+                nb = 1;
+            }
+        }
+        if (sb.length() > 0) {
+            sb.append(" ");
+        }
+        sb.append(last);
+        if (nb > 1) {
+            sb.append("(").append(nb).append(")");
+        }
+        return sb.toString();
+    }
+
+    protected Token start(String str, String missing) {
+        stack.addLast(missing);
+        return expect(str);
+    }
+
+    protected Token end(String str) {
+        Token t = expect(str);
+        stack.removeLast();
+        return t;
+    }
+
+    protected Token expect(String str)
+    {
+        Token start = next();
+        if (start == null)
+        {
+            throw new EOFError(tz.line, tz.column,
+                    "unexpected EOT looking for '" + str + "",
+                    getMissing(), str);
+        }
+        if (!Token.eq(str, start))
+        {
+            throw new SyntaxError(start.line, start.column, "expected '" + str + "' but got '" + start.toString() + "'");
+        }
+        return start;
+    }
+
+    protected Token whole(List<? extends Token> tokens, int index)
+    {
+        if (tokens.isEmpty())
+        {
+            index = Math.min(index, tz.text().length());
+            return tz.text().subSequence(index, index);
+        }
+        Token b = tokens.get(0);
+        Token e = tokens.get(tokens.size() - 1);
+        return whole(b, e);
+    }
+
+    protected Token whole(Token b, Token e)
+    {
+        return tz.text.subSequence(b.start, e.start + e.length());
+    }
+
 }
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Pipe.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Pipe.java
index 7531835..ed2dba9 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Pipe.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Pipe.java
@@ -24,8 +24,8 @@
 import java.io.PipedOutputStream;
 import java.io.PrintStream;
 import java.lang.reflect.Method;
-import java.util.List;
 
+import org.apache.felix.gogo.runtime.Parser.Executable;
 import org.apache.felix.service.command.Converter;
 
 public class Pipe extends Thread
@@ -40,7 +40,7 @@
     Closure closure;
     Exception exception;
     Object result;
-    List<Token> statement;
+    Executable executable;
 
     public static Object[] mark()
     {
@@ -55,11 +55,11 @@
         tErr.set((PrintStream) mark[2]);
     }
 
-    public Pipe(Closure closure, List<Token> statement)
+    public Pipe(Closure closure, Executable executable)
     {
-        super("pipe-" + statement);
+        super("pipe-" + executable);
         this.closure = closure;
-        this.statement = statement;
+        this.executable = executable;
 
         in = tIn.get();
         out = tOut.get();
@@ -68,7 +68,7 @@
 
     public String toString()
     {
-        return "pipe<" + statement + "> out=" + out;
+        return "pipe<" + executable + "> out=" + out;
     }
 
     public void setIn(InputStream in)
@@ -105,7 +105,7 @@
 
         try
         {
-            result = closure.executeStatement(statement);
+            result = closure.execute(executable);
             if (result != null && pout != null)
             {
                 if (!Boolean.FALSE.equals(closure.session().get(".FormatPipe")))
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Reflective.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Reflective.java
index d314fe0..442505a 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Reflective.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Reflective.java
@@ -346,7 +346,7 @@
      * to allow the "best" conversion to be determined.
      * @return converted arg or NO_MATCH if no conversion possible.
      */
-    private static Object coerce(CommandSession session, Class<?> type, Object arg,
+    public static Object coerce(CommandSession session, Class<?> type, Object arg,
         int[] convert)
     {
         if (arg == null)
@@ -386,7 +386,7 @@
         // all following conversions cost 2 points
         convert[0] += 2;
 
-        Object converted = session.convert(type, arg);
+        Object converted = ((CommandSessionImpl) session).doConvert(type, arg);
         if (converted != null)
         {
             return converted;
@@ -399,6 +399,17 @@
             return string;
         }
 
+        if (type.isEnum())
+        {
+            for (Object o : type.getEnumConstants())
+            {
+                if (o.toString().equalsIgnoreCase(string))
+                {
+                    return o;
+                }
+            }
+        }
+
         if (type.isPrimitive())
         {
             type = primitiveToObject(type);
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/SyntaxError.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/SyntaxError.java
index b359938..ff87519 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/SyntaxError.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/SyntaxError.java
@@ -23,12 +23,19 @@
     private static final long serialVersionUID = 1L;
     private final int line;
     private final int column;
+    private final Token statement;
 
     public SyntaxError(int line, int column, String message)
     {
+        this(line, column, message, null);
+    }
+
+    public SyntaxError(int line, int column, String message, Token statement)
+    {
         super(message);
         this.line = line;
         this.column = column;
+        this.statement = statement;
     }
     
     public int column()
@@ -40,4 +47,9 @@
     {
         return line;
     }
+
+    public Token statement() {
+        return statement;
+    }
+
 }
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Token.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Token.java
index 93320fe..bbc087a 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Token.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Token.java
@@ -18,63 +18,113 @@
  */
 package org.apache.felix.gogo.runtime;
 
-import org.apache.felix.gogo.runtime.Tokenizer.Type;
+public class Token implements CharSequence {
 
-public class Token implements CharSequence
-{
-    Type type; 
-    CharSequence value;
-    short line;
-    short column;
-    
-    public Token(Type type, CharSequence value, short line, short column)
+    protected final char[] ch;
+    protected final int start;
+    protected final int length;
+    protected final int line;
+    protected final int column;
+
+    public Token(CharSequence cs)
     {
-        this.type = type;
-        this.value = value;
-        this.line = line;
-        this.column = column;
+        if (cs instanceof Token)
+        {
+            Token ca = (Token) cs;
+            this.ch = ca.ch;
+            this.start = ca.start;
+            this.length = ca.length;
+            this.line = ca.line;
+            this.column = ca.column;
+        }
+        else
+        {
+            this.ch = cs.toString().toCharArray();
+            this.start = 0;
+            this.length = ch.length;
+            this.line = 0;
+            this.column = 0;
+        }
     }
 
-    @Override
-    public String toString()
+    public Token(char[] _ch, int _start, int _length, int _line, int _col)
     {
-        //return type + "<" + value + ">";
-        return null == value ? type.toString() : value.toString();
+        this.ch = _ch;
+        this.start = _start;
+        this.length = _length;
+        this.line = _line;
+        this.column = _col;
     }
-    
-    public char charAt(int index)
+
+    public int line()
     {
-        return  value.charAt(index);
+        return line;
+    }
+
+    public int column()
+    {
+        return column;
+    }
+
+    public int start()
+    {
+        return start;
     }
 
     public int length()
     {
-        return (null == value ? 0 : value.length());
+        return this.length;
     }
 
-    public CharSequence subSequence(int start, int end)
+    public char charAt(int index)
     {
-        return value.subSequence(start, end);
+        return this.ch[this.start + index];
     }
-    
-    public String source()
+
+    public Token subSequence(int start, int end)
     {
-        switch (type)
+        int line = this.line;
+        int col = this.column;
+        for (int i = this.start; i < this.start + start; i++)
         {
-            case WORD:
-                return value.toString();
-                
-            case CLOSURE:
-                return "{" + value + "}";
-                
-            case EXECUTION:
-                return "(" + value + ")";
-                
-            case ARRAY:
-                return "[" + value + "]";
-                
-            default:
-                return type.toString();
+            if (ch[i] == '\n')
+            {
+                line++;
+                col = 0;
+            }
+            else
+            {
+                col++;
+            }
         }
+        return new Token(this.ch, this.start + start, end - start, line, col);
     }
+
+    public String toString()
+    {
+        return new String(this.ch, this.start, this.length);
+    }
+
+    public static boolean eq(CharSequence cs1, CharSequence cs2)
+    {
+        if (cs1 == cs2)
+        {
+            return true;
+        }
+        int l1 = cs1.length();
+        int l2 = cs2.length();
+        if (l1 != l2)
+        {
+            return false;
+        }
+        for (int i = 0; i < l1; i++)
+        {
+            if (cs1.charAt(i) != cs2.charAt(i))
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
 }
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Tokenizer.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Tokenizer.java
index c2fd39b..390becc 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Tokenizer.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Tokenizer.java
@@ -18,782 +18,138 @@
  */
 package org.apache.felix.gogo.runtime;
 
-/**
- * Bash-like tokenizer.
- * 
- * Single and double quotes are just like Bash - single quotes escape everything
- * (including backslashes), newlines are allowed in quotes.
- * backslash-newline indicates a line continuation and is removed.
- * 
- * Variable expansion is just like Bash: $NAME or ${NAME[[:][-+=?WORD]},
- * except it can yield any Object. Variables expanded within double-quotes,
- * or adjacent to a String are converted to String.
- * 
- * Unlike bash, indirect variable expansion is supported using ${$NAME}.
- * 
- * Only a single variable assignment is recognized, with '=' being the second token.
- * (Bash allows name1=value1 name2=value2 ... command args)
- * 
- * Comments can only start where white space is allowed:
- * # or // starts a line comment, /* starts a block comment.
- * The following common uses do NOT start comments:
- *    ls http://example.com#anchor
- *    ls $dir/*.java
- * 
- * @see http://wiki.bash-hackers.org/syntax/basicgrammar
- */
-public class Tokenizer
+public class Tokenizer extends BaseTokenizer
 {
-    public enum Type
-    {
-        ASSIGN('='), PIPE('|'), SEMICOLON(';'), NEWLINE, ARRAY, CLOSURE, EXPR, EXECUTION, WORD, EOT;
 
-        private char c;
+    protected boolean inArray;
+    protected int word = 0;
 
-        Type()
-        {
-        }
-
-        Type(char c)
-        {
-            this.c = c;
-        }
-
-        @Override
-        public String toString()
-        {
-            return (c == 0 ? super.toString() : "'" + c + "'");
-        }
-    }
-
-    private static final boolean DEBUG = false;
-    private static final char EOT = (char) -1;
-
-    private final CharSequence text;
-    private final Evaluate evaluate;
-    private final boolean inArray;
-    private final boolean inQuote;
-
-    private Type type = Type.NEWLINE;
-    private CharSequence value;
-    private Token token;
-
-    private short line;
-    private short column;
-    private char ch;
-    private int index;
-    private boolean firstWord;
+    protected Token pushed;
+    protected Token last;
 
     public Tokenizer(CharSequence text)
     {
-        this(text, null, false);
+        super(text);
     }
 
-    public Tokenizer(CharSequence text, Evaluate evaluate, boolean inQuote)
+    public Token text()
     {
-        this.text = text;
-        this.evaluate = evaluate;
-        this.inQuote = inQuote;
-        index = 0;
-        line = column = 1;
+        return text;
+    }
 
-        boolean array = false;
-
-        if (text instanceof Token)
+    public Token next()
+    {
+        if (pushed != null)
         {
-            Token t = (Token) text;
-            line = t.line;
-            column = t.column;
-            array = (Type.ARRAY == t.type);
+            Token t = pushed;
+            pushed = null;
+            return t;
         }
-
-        inArray = array;
-        getch();
-
-        if (DEBUG)
-        {
-            if (inArray)
-                System.err.println("Tokenizer[" + text + "]");
-            else
-                System.err.println("Tokenizer<" + text + ">");
-        }
-    }
-
-    public Type type()
-    {
-        return type;
-    }
-
-    public CharSequence value()
-    {
-        return value;
-    }
-
-    public Token token()
-    {
-        return token;
-    }
-
-    public Type next()
-    {
-        final Type prevType = type;
-        token = null;
-        value = null;
-
-        short tLine;
-        short tColumn;
-
+        skipSpace(last == null || Token.eq(last, "\n"));
+        int start = index - 1;
         while (true)
         {
-            skipSpace();
-            tLine = line;
-            tColumn = column;
-
             switch (ch)
             {
                 case EOT:
-                    type = Type.EOT;
-                    break;
-
-                case '\n':
-                    getch();
-                    if (inArray)
-                        continue;
-                    // only return NEWLINE once and not if not preceded by ; or |
-                    switch (prevType)
-                    {
-                        case PIPE:
-                        case SEMICOLON:
-                        case NEWLINE:
-                            continue;
-
-                        default:
-                            type = Type.NEWLINE;
-                            break;
-                    }
-                    break;
-
+                    return token(start);
+                case '[':
+                    word = 0;
+                    inArray = true;
+                    return token(start);
+                case ']':
+                    inArray = false;
+                    word++;
+                    return token(start);
                 case '{':
                 case '(':
-                case '[':
-                    value = group();
-                    getch();
-                    break;
-
-                case ';':
-                    getch();
-                    type = Type.SEMICOLON;
-                    break;
-
-                case '|':
-                    getch();
-                    type = Type.PIPE;
-                    break;
-
-                case '=':
-                    if (firstWord || inArray)
+                    if (start == index - 1)
                     {
+                        word = 0;
+                        return token(start);
+                    }
+                    else
+                    {
+                        if (ch == '{')
+                        {
+                            find('}', '{');
+                        }
+                        else
+                        {
+                            find(')', '(');
+                        }
                         getch();
-                        type = Type.ASSIGN;
                         break;
                     }
-                    // fall through
-                default:
-                    value = word();
-                    type = Type.WORD;
-            }
-
-            firstWord = (Type.WORD == type && (Type.WORD != prevType && Type.ASSIGN != prevType));
-            token = new Token(type, value, tLine, tColumn);
-
-            if (DEBUG)
-            {
-                System.err.print("<" + type + ">");
-                if (Type.EOT == type)
-                {
-                    System.err.println();
-                }
-            }
-
-            return type;
-        }
-    }
-
-    private CharSequence word()
-    {
-        int start = index - 1;
-        int skipCR = 0;
-
-        do
-        {
-            switch (ch)
-            {
-                case '\n':
-                    if (index >= 2 && text.charAt(index - 2) == '\r')
-                        skipCR = 1;
-                    // fall through
-                case '=':
-                    if ((Type.WORD == type || Type.ASSIGN == type) && '=' == ch
-                        && !inArray)
-                        continue;
-                    // fall through
+                case ';':
+                case '|':
+                    word = 0;
+                    return token(start);
+                case '}':
+                case ')':
                 case ' ':
                 case '\t':
-                case '|':
-                case ';':
-                    return text.subSequence(start, index - 1 - skipCR);
-
-                case '(':
-                case '{':
-                    group();
+                case '\n':
+                case '\r':
+                    word++;
+                    return token(start);
+                case '=':
+                    if (inArray || word < 1 || index == start + 1)
+                    {
+                        word++;
+                        return token(start);
+                    }
+                    getch();
                     break;
-
                 case '\\':
                     escape();
+                    getch();
                     break;
-
                 case '\'':
                 case '"':
                     skipQuote();
+                    getch();
                     break;
-            }
-        }
-        while (getch() != EOT);
-
-        return text.subSequence(start, index - 1);
-    }
-
-    private CharSequence group()
-    {
-        final char push = ch;
-        final char pop;
-
-        switch (ch)
-        {
-            case '{':
-                type = Type.CLOSURE;
-                pop = '}';
-                break;
-            case '(':
-                type = Type.EXECUTION;
-                pop = ')';
-                break;
-            case '[':
-                type = Type.ARRAY;
-                pop = ']';
-                break;
-            default:
-                assert false;
-                pop = 0;
-        }
-
-        short sLine = line;
-        short sCol = column;
-        int start = index;
-        int depth = 1;
-
-        while (true)
-        {
-            boolean comment = false;
-
-            switch (ch)
-            {
-                case '{':
-                case '(':
-                case '[':
-                case '\n':
-                    comment = true;
-                    break;
-            }
-
-            if (getch() == EOT)
-            {
-                throw new EOFError(sLine, sCol, "unexpected EOT looking for matching '"
-                    + pop + "'");
-            }
-
-            // don't recognize comments that start within a word
-            if (comment || isBlank(ch))
-                skipSpace();
-
-            switch (ch)
-            {
-                case '"':
-                case '\'':
-                    skipQuote();
-                    break;
-
-                case '\\':
-                    ch = escape();
-                    break;
-
                 default:
-                    if (push == ch)
-                        depth++;
-                    else if (pop == ch && --depth == 0)
-                        return text.subSequence(start, index - 1);
-            }
-        }
-
-    }
-
-    private char escape()
-    {
-        assert '\\' == ch;
-
-        switch (getch())
-        {
-            case 'u':
-                getch();
-                getch();
-                getch();
-                getch();
-
-                if (EOT == ch)
-                {
-                    throw new EOFError(line, column, "unexpected EOT in \\u escape");
-                }
-
-                String u = text.subSequence(index - 4, index).toString();
-
-                try
-                {
-                    return (char) Integer.parseInt(u, 16);
-                }
-                catch (NumberFormatException e)
-                {
-                    throw new SyntaxError(line, column, "bad unicode escape: \\u" + u);
-                }
-
-            case EOT:
-                throw new EOFError(line, column, "unexpected EOT in \\ escape");
-
-            case '\n':
-                return '\0'; // line continuation
-
-            case '\\':
-            case '\'':
-            case '"':
-            case '$':
-                return ch;
-
-            default:
-                return ch;
-        }
-    }
-
-    private void skipQuote()
-    {
-        assert '\'' == ch || '"' == ch;
-        final char quote = ch;
-        final short sLine = line;
-        final short sCol = column;
-
-        while (getch() != EOT)
-        {
-            if (quote == ch)
-                return;
-
-            if ((quote == '"') && ('\\' == ch))
-                escape();
-        }
-
-        throw new EOFError(sLine, sCol, "unexpected EOT looking for matching quote: "
-            + quote);
-    }
-
-    private void skipSpace()
-    {
-        while (true)
-        {
-            while (isBlank(ch))
-            {
-                getch();
-            }
-
-            // skip continuation lines, but not other escapes
-            if (('\\' == ch) && (peek() == '\n'))
-            {
-                getch();
-                getch();
-                continue;
-            }
-
-            // skip comments
-            if (('/' == ch) || ('#' == ch))
-            {
-                if (('#' == ch) || (peek() == '/'))
-                {
-                    while ((getch() != EOT) && ('\n' != ch))
-                    {
-                    }
-                    continue;
-                }
-                else if ('*' == peek())
-                {
-                    short sLine = line;
-                    short sCol = column;
                     getch();
-
-                    while ((getch() != EOT) && !(('*' == ch) && (peek() == '/')))
-                    {
-                    }
-
-                    if (EOT == ch)
-                    {
-                        throw new EOFError(sLine, sCol,
-                            "unexpected EOT looking for closing comment: */");
-                    }
-
-                    getch();
-                    getch();
-                    continue;
-                }
-            }
-
-            break;
-        }
-    }
-
-    private boolean isBlank(char ch)
-    {
-        return ' ' == ch || '\t' == ch;
-    }
-
-    private boolean isName(char ch)
-    {
-        return Character.isJavaIdentifierPart(ch) && (ch != '$') || ('.' == ch);
-    }
-
-    /**
-     * expand variables, quotes and escapes in word.
-     * @param vars
-     * @return
-     * @throws Exception 
-     */
-    public static Object expand(CharSequence word, Evaluate eval) throws Exception
-    {
-        return expand(word, eval, false);
-    }
-
-    private static Object expand(CharSequence word, Evaluate eval, boolean inQuote) throws Exception
-    {
-        final String special = "%$\\\"'";
-        int i = word.length();
-
-        while ((--i >= 0) && (special.indexOf(word.charAt(i)) == -1))
-        {
-        }
-
-        // shortcut if word doesn't contain any special characters
-        if (i < 0)
-            return word;
-
-        return new Tokenizer(word, eval, inQuote).expand();
-    }
-
-    public Object expand(CharSequence word, short line, short column) throws Exception
-    {
-        return expand(new Token(Type.WORD, word, line, column), evaluate, inQuote);
-    }
-
-    private Token word(CharSequence value)
-    {
-        return new Token(Type.WORD, value, line, column);
-    }
-
-    private Object expand() throws Exception
-    {
-        StringBuilder buf = new StringBuilder();
-
-        while (ch != EOT)
-        {
-            int start = index;
-
-            switch (ch)
-            {
-                case '%':
-                    Object exp = expandExp();
-
-                    if (EOT == ch && buf.length() == 0)
-                    {
-                        return exp;
-                    }
-
-                    if (null != exp)
-                    {
-                        buf.append(exp);
-                    }
-
-                    continue; // expandVar() has already read next char
-
-                case '$':
-                    Object val = expandVar();
-
-                    if (EOT == ch && buf.length() == 0)
-                    {
-                        return val;
-                    }
-
-                    if (null != val)
-                    {
-                        buf.append(val);
-                    }
-
-                    continue; // expandVar() has already read next char
-
-                case '\\':
-                    ch = (inQuote && ("u$\\\n\"".indexOf(peek()) == -1)) ? '\\'
-                        : escape();
-
-                    if (ch != '\0') // ignore line continuation
-                    {
-                        buf.append(ch);
-                    }
-
                     break;
-
-                case '"':
-                    Token ww = word(null);
-                    skipQuote();
-                    ww.value = text.subSequence(start, index - 1);
-                    value = ww;
-                    Object expand = expand(value, evaluate, true);
-
-                    if (eot() && buf.length() == 0 && value == expand)
-                    {
-                        // FELIX-2468 avoid returning CharSequence implementation
-                        return ww.value.toString();
-                    }
-
-                    if (null != expand)
-                    {
-                        buf.append(expand.toString());
-                    }
-                    break;
-
-                case '\'':
-                    if (!inQuote)
-                    {
-                        skipQuote();
-                        value = text.subSequence(start, index - 1);
-
-                        if (eot() && buf.length() == 0)
-                        {
-                            return value;
-                        }
-
-                        buf.append(value);
-                        break;
-                    }
-                    // else fall through
-                default:
-                    buf.append(ch);
             }
-
-            getch();
-        }
-
-        return buf.toString();
-    }
-
-    private Object expandExp() throws Exception
-    {
-        assert '%' == ch;
-        Object val;
-
-        if (getch() == '(')
-        {
-            short sLine = line;
-            short sCol = column;
-            val = evaluate.eval(new Token(Type.EXPR, group(), sLine, sCol));
-            getch();
-            return val;
-        }
-        else
-        {
-            throw new SyntaxError(line, column, "bad expression: " + text);
         }
     }
 
-    private Object expandVar() throws Exception
+    private Token token(int start)
     {
-        assert '$' == ch;
-        Object val;
-
-        if (getch() != '{')
+        if (start == index - 1)
         {
-            if ('(' == ch)
-            { // support $(...) FELIX-2433
-                short sLine = line;
-                short sCol = column;
-                val = evaluate.eval(new Token(Type.EXECUTION, group(), sLine, sCol));
+            if (ch == EOT)
+            {
+                return null;
+            }
+            if (ch == '\r' && peek() == '\n')
+            {
                 getch();
             }
-            else
-            {
-                int start = index - 1;
-                while (isName(ch))
-                {
-                    getch();
-                }
-
-                if (index - 1 == start)
-                {
-                    val = "$";
-                }
-                else
-                {
-                    String name = text.subSequence(start, index - 1).toString();
-                    val = evaluate.get(name);
-                }
-            }
+            getch();
+            last = text.subSequence(index - 2, index - 1);
         }
         else
         {
-            // ${NAME[[:]-+=?]WORD}
-            short sLine = line;
-            short sCol = column;
-            CharSequence group = group();
-            char c;
-            int i = 0;
+            last = text.subSequence(start, index - 1);
+        }
+        return last;
+    }
 
-            while (i < group.length())
-            {
-                switch (group.charAt(i))
-                {
-                    case ':':
-                    case '-':
-                    case '+':
-                    case '=':
-                    case '?':
-                        break;
+    public void push(Token token)
+    {
+        this.pushed = token;
+    }
 
-                    default:
-                        ++i;
-                        continue;
-                }
-                break;
-            }
-
-            sCol += i;
-
-            String name = String.valueOf(expand(group.subSequence(0, i), sLine, sCol));
-
-            for (int j = 0; j < name.length(); ++j)
-            {
-                if (!isName(name.charAt(j)))
-                {
-                    throw new SyntaxError(sLine, sCol, "bad name: ${" + group + "}");
-                }
-            }
-
-            val = evaluate.get(name);
-
-            if (i < group.length())
-            {
-                c = group.charAt(i++);
-                if (':' == c)
-                {
-                    c = (i < group.length() ? group.charAt(i++) : EOT);
-                }
-
-                CharSequence word = group.subSequence(i, group.length());
-
-                switch (c)
-                {
-                    case '-':
-                    case '=':
-                        if (null == val)
-                        {
-                            val = expand(word, evaluate, false);
-                            if ('=' == c)
-                            {
-                                evaluate.put(name, val);
-                            }
-                        }
-                        break;
-
-                    case '+':
-                        if (null != val)
-                        {
-                            val = expand(word, evaluate, false);
-                        }
-                        break;
-
-                    case '?':
-                        if (null == val)
-                        {
-                            val = expand(word, evaluate, false);
-                            if (null == val || val.toString().length() == 0)
-                            {
-                                val = "parameter not set";
-                            }
-                            throw new IllegalArgumentException(name + ": " + val);
-                        }
-                        break;
-
-                    default:
-                        throw new SyntaxError(sLine, sCol, "bad substitution: ${" + group
-                            + "}");
-                }
-            }
+    public void skip(int length)
+    {
+        while (--length >= 0)
+        {
             getch();
         }
-
-        return val;
-    }
-
-    /**
-     * returns true if getch() will return EOT
-     * @return
-     */
-    private boolean eot()
-    {
-        return index >= text.length();
-    }
-
-    private char getch()
-    {
-        return ch = getch(false);
-    }
-
-    private char peek()
-    {
-        return getch(true);
-    }
-
-    private char getch(boolean peek)
-    {
-        if (eot())
-        {
-            if (!peek)
-            {
-                ++index;
-                ch = EOT;
-            }
-            return EOT;
-        }
-
-        int current = index;
-        char c = text.charAt(index++);
-
-        if (('\r' == c) && !eot() && (text.charAt(index) == '\n'))
-            c = text.charAt(index++);
-
-        if (peek)
-        {
-            index = current;
-        }
-        else if ('\n' == c)
-        {
-            ++line;
-            column = 0;
-        }
-        else
-            ++column;
-
-        return c;
     }
 
 }
diff --git a/gogo/runtime/src/main/java/org/apache/felix/service/command/CommandSession.java b/gogo/runtime/src/main/java/org/apache/felix/service/command/CommandSession.java
index 6aad74f..c81c8af 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/service/command/CommandSession.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/service/command/CommandSession.java
@@ -74,7 +74,7 @@
      * @param name  Name of the variable.
      * @param value Value of the variable
      */
-    void put(String name, Object value);
+    Object put(String name, Object value);
 
     /**
      * Convert an object to string form (CharSequence). The level is defined in
diff --git a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/BaseTestCase.java b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/AbstractParserTest.java
similarity index 62%
rename from gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/BaseTestCase.java
rename to gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/AbstractParserTest.java
index 488e49b..7522397 100644
--- a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/BaseTestCase.java
+++ b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/AbstractParserTest.java
@@ -16,27 +16,32 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
 package org.apache.felix.gogo.runtime;
 
 import junit.framework.TestCase;
+import org.apache.felix.gogo.runtime.threadio.ThreadIOImpl;
 
-/**
- * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
- */
-abstract class BaseTestCase extends TestCase
-{
-    protected Context m_ctx;
+public abstract class AbstractParserTest extends TestCase {
+
+    private ThreadIOImpl threadIO;
 
     @Override
-    protected final void setUp() throws Exception
-    {
-        m_ctx = new Context(true);
+    protected void setUp() throws Exception {
+        super.setUp();
+        threadIO = new ThreadIOImpl();
+        threadIO.start();
     }
 
     @Override
-    protected final void tearDown() throws Exception
-    {
-        m_ctx.stop();
+    protected void tearDown() throws Exception {
+        threadIO.stop();
+        super.tearDown();
     }
+
+    public class Context extends org.apache.felix.gogo.runtime.Context {
+        public Context() {
+            super(AbstractParserTest.this.threadIO);
+        }
+    }
+
 }
diff --git a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/Context.java b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/Context.java
index bd40bf2..e4096d2 100644
--- a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/Context.java
+++ b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/Context.java
@@ -18,42 +18,22 @@
  */
 package org.apache.felix.gogo.runtime;
 
-import org.apache.felix.gogo.runtime.threadio.ThreadIOImpl;
 import org.apache.felix.service.command.CommandSession;
+import org.apache.felix.service.threadio.ThreadIO;
 
 public class Context extends CommandProcessorImpl
 {
     public static final String EMPTY = "";
-
-    private static final ThreadIOImpl threadio;
+    
     private final CommandSession session;
 
-    static
-    {
-        threadio = new ThreadIOImpl();
-        threadio.start();
-    }
-
-    public Context(boolean foo)
+    public Context(ThreadIO threadio)
     {
         super(threadio);
         addCommand("osgi", this, "addCommand");
         addCommand("osgi", this, "removeCommand");
         addCommand("osgi", this, "eval");
-        session = (CommandSessionImpl) createSession(System.in, System.out, System.err);
-    }
-
-    @Override
-    public void stop()
-    {
-        try
-        {
-            super.stop();
-        }
-        finally
-        {
-            threadio.stop();
-        }
+        session = createSession(System.in, System.out, System.err);
     }
 
     public Object execute(CharSequence source) throws Exception
@@ -76,9 +56,9 @@
         addCommand("test", target, function);
     }
 
-    public void set(String name, Object value)
+    public Object set(String name, Object value)
     {
-        session.put(name, value);
+        return session.put(name, value);
     }
 
     public Object get(String name)
diff --git a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestCoercion.java b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestCoercion.java
index 7ab32a1..f4ea27c 100644
--- a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestCoercion.java
+++ b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestCoercion.java
@@ -22,7 +22,7 @@
 import org.apache.felix.service.command.Descriptor;
 import org.apache.felix.service.command.Parameter;
 
-public class TestCoercion extends BaseTestCase
+public class TestCoercion extends AbstractParserTest
 {
     public boolean fBool(boolean t)
     {
@@ -57,31 +57,32 @@
 
     public void testSimpleTypes() throws Exception
     {
-        m_ctx.addCommand("fBool", this);
-        m_ctx.addCommand("fDouble", this);
-        m_ctx.addCommand("fInt", this);
-        m_ctx.addCommand("fLong", this);
-        m_ctx.addCommand("fString", this);
+        Context c = new Context();
+        c.addCommand("fBool", this);
+        c.addCommand("fDouble", this);
+        c.addCommand("fInt", this);
+        c.addCommand("fLong", this);
+        c.addCommand("fString", this);
 
-        assertEquals("fBool true", true, m_ctx.execute("fBool true"));
-        assertEquals("fBool 'false'", false, m_ctx.execute("fBool 'false'"));
-
-        assertEquals("fDouble 11", 11.0, m_ctx.execute("fDouble 11"));
-        assertEquals("fDouble '11'", 11.0, m_ctx.execute("fDouble '11'"));
-
-        assertEquals("fInt 22", 22, m_ctx.execute("fInt 22"));
-        assertEquals("fInt '23'", 23, m_ctx.execute("fInt '23'"));
-        assertEquals("fInt 1 2", "array", m_ctx.execute("fInt 1 2"));
-
-        assertEquals("fLong 33", 33L, m_ctx.execute("fLong 33"));
-        assertEquals("fLong '34'", 34L, m_ctx.execute("fLong '34'"));
-
-        assertEquals("fString wibble", "wibble", m_ctx.execute("fString wibble"));
-        assertEquals("fString 'wibble'", "wibble", m_ctx.execute("fString 'wibble'"));
+        assertEquals("fBool true", true, c.execute("fBool true"));
+        assertEquals("fBool 'false'", false, c.execute("fBool 'false'"));
+        
+        assertEquals("fDouble 11", 11.0, c.execute("fDouble 11"));
+        assertEquals("fDouble '11'", 11.0, c.execute("fDouble '11'"));
+        
+        assertEquals("fInt 22", 22, c.execute("fInt 22"));
+        assertEquals("fInt '23'", 23, c.execute("fInt '23'"));
+        assertEquals("fInt 1 2", "array", c.execute("fInt 1 2"));
+        
+        assertEquals("fLong 33", 33L, c.execute("fLong 33"));
+        assertEquals("fLong '34'", 34L, c.execute("fLong '34'"));
+        
+        assertEquals("fString wibble", "wibble", c.execute("fString wibble"));
+        assertEquals("fString 'wibble'", "wibble", c.execute("fString 'wibble'"));
 
         try
         {
-            Object r = m_ctx.execute("fString ");
+            Object r = c.execute("fString ");
             fail("too few args: expected IllegalArgumentException, got: " + r);
         }
         catch (IllegalArgumentException e)
@@ -90,7 +91,7 @@
 
         try
         {
-            Object r = m_ctx.execute("fString a b");
+            Object r = c.execute("fString a b");
             fail("too many args: expected IllegalArgumentException, got: " + r);
         }
         catch (IllegalArgumentException e)
@@ -99,7 +100,7 @@
 
         try
         {
-            Object r = m_ctx.execute("fLong string");
+            Object r = c.execute("fLong string");
             fail("wrong arg type: expected IllegalArgumentException, got: " + r);
         }
         catch (IllegalArgumentException e)
@@ -119,11 +120,12 @@
 
     public void testBestCoercion() throws Exception
     {
-        m_ctx.addCommand("bundles", this);
+        Context c = new Context();
+        c.addCommand("bundles", this);
 
-        assertEquals("bundles myloc", "string", m_ctx.execute("bundles myloc"));
-        assertEquals("bundles 1", "long", m_ctx.execute("bundles 1"));
-        assertEquals("bundles '1'", "string", m_ctx.execute("bundles '1'"));
+        assertEquals("bundles myloc", "string", c.execute("bundles myloc"));
+        assertEquals("bundles 1", "long", c.execute("bundles 1"));
+        assertEquals("bundles '1'", "string", c.execute("bundles '1'"));
     }
 
     @Descriptor("list all installed bundles")
@@ -145,23 +147,24 @@
 
     public void testParameter0() throws Exception
     {
-        m_ctx.addCommand("p0", this);
-        m_ctx.addCommand("p01", this);
+        Context c = new Context();
+        c.addCommand("p0", this);
+        c.addCommand("p01", this);
 
-        assertEquals("p0", "false:false", m_ctx.execute("p0"));
-        assertEquals("p0 -l", "true:false", m_ctx.execute("p0 -l"));
-        assertEquals("p0 --location", "true:false", m_ctx.execute("p0 --location"));
-        assertEquals("p0 -l -s", "true:true", m_ctx.execute("p0 -l -s"));
-        assertEquals("p0 -s -l", "true:true", m_ctx.execute("p0 -s -l"));
+        assertEquals("p0", "false:false", c.execute("p0"));
+        assertEquals("p0 -l", "true:false", c.execute("p0 -l"));
+        assertEquals("p0 --location", "true:false", c.execute("p0 --location"));
+        assertEquals("p0 -l -s", "true:true", c.execute("p0 -l -s"));
+        assertEquals("p0 -s -l", "true:true", c.execute("p0 -s -l"));
         try
         {
-            Object r = m_ctx.execute("p0 wibble");
+            Object r = c.execute("p0 wibble");
             fail("too many args: expected IllegalArgumentException, got: " + r);
         }
         catch (IllegalArgumentException e)
         {
         }
-        assertEquals("p01 -f", true, m_ctx.execute("p01 -f"));
+        assertEquals("p01 -f", true, c.execute("p01 -f"));
     }
 
     public String p1(
@@ -172,16 +175,17 @@
 
     public void testParameter1() throws Exception
     {
-        m_ctx.addCommand("p1", this);
+        Context c = new Context();
+        c.addCommand("p1", this);
 
-        assertEquals("no parameter", "absent", m_ctx.execute("p1"));
+        assertEquals("no parameter", "absent", c.execute("p1"));
 
         // FELIX-2894
-        assertEquals("correct parameter", "wibble", m_ctx.execute("p1 -p wibble"));
+        assertEquals("correct parameter", "wibble", c.execute("p1 -p wibble"));
 
         try
         {
-            Object r = m_ctx.execute("p1 -p");
+            Object r = c.execute("p1 -p");
             fail("missing parameter: expected IllegalArgumentException, got: " + r);
         }
         catch (IllegalArgumentException e)
@@ -190,11 +194,12 @@
 
         try
         {
-            Object r = m_ctx.execute("p1 -X");
+            Object r = c.execute("p1 -X");
             fail("wrong parameter: expected IllegalArgumentException, got: " + r);
         }
         catch (IllegalArgumentException e)
         {
         }
     }
+
 }
diff --git a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser.java b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser.java
index 7250b0a..a034f2b 100644
--- a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser.java
+++ b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser.java
@@ -27,28 +27,34 @@
 import java.util.List;
 import java.util.regex.Pattern;
 
+import org.apache.felix.gogo.runtime.Parser.Pipeline;
+import org.apache.felix.gogo.runtime.Parser.Program;
+import org.apache.felix.gogo.runtime.Parser.Sequence;
+import org.apache.felix.gogo.runtime.Parser.Statement;
 import org.apache.felix.service.command.CommandSession;
 import org.apache.felix.service.command.Function;
 
-public class TestParser extends BaseTestCase
+public class TestParser extends AbstractParserTest
 {
     int beentheredonethat = 0;
 
     public void testEvaluatation() throws Exception
     {
-        m_ctx.addCommand("echo", this);
-        m_ctx.addCommand("capture", this);
+        Context c = new Context();
+        c.addCommand("echo", this);
+        c.addCommand("capture", this);
 
-        assertEquals("a", m_ctx.execute("echo a | capture"));
-        assertEquals("a", m_ctx.execute("(echo a) | capture"));
-        assertEquals("a", m_ctx.execute("((echo a)) | capture"));
+        assertEquals("a", c.execute("echo a | capture"));
+        assertEquals("a", c.execute("(echo a) | capture"));
+        assertEquals("a", c.execute("((echo a)) | capture"));
     }
 
     public void testUnknownCommand() throws Exception
     {
+        Context c = new Context();
         try
         {
-            m_ctx.execute("echo");
+            c.execute("echo");
             fail("Execution should have failed due to missing command");
         }
         catch (IllegalArgumentException e)
@@ -59,120 +65,129 @@
 
     public void testSpecialValues() throws Exception
     {
-        assertEquals(false, m_ctx.execute("false"));
-        assertEquals(true, m_ctx.execute("true"));
-        assertEquals(null, m_ctx.execute("null"));
+        Context c = new Context();
+        assertEquals(false, c.execute("false"));
+        assertEquals(true, c.execute("true"));
+        assertEquals(null, c.execute("null"));
     }
 
     public void testQuotes() throws Exception
     {
-        m_ctx.addCommand("echo", this);
-        m_ctx.set("c", "a");
+        Context c = new Context();
+        c.addCommand("echo", this);
+        c.set("c", "a");
 
-        assertEquals("a b", m_ctx.execute("echo a b"));
-        assertEquals("a b", m_ctx.execute("echo 'a b'"));
-        assertEquals("a b", m_ctx.execute("echo \"a b\""));
-        assertEquals("a b", m_ctx.execute("echo a  b"));
-        assertEquals("a  b", m_ctx.execute("echo 'a  b'"));
-        assertEquals("a  b", m_ctx.execute("echo \"a  b\""));
-        assertEquals("a b", m_ctx.execute("echo $c  b"));
-        assertEquals("$c  b", m_ctx.execute("echo '$c  b'"));
-        assertEquals("a  b", m_ctx.execute("echo \"$c  b\""));
-        assertEquals("a b", m_ctx.execute("echo ${c}  b"));
-        assertEquals("${c}  b", m_ctx.execute("echo '${c}  b'"));
-        assertEquals("a  b", m_ctx.execute("echo \"${c}  b\""));
-        assertEquals("aa", m_ctx.execute("echo $c$c"));
-        assertEquals("a ;a", m_ctx.execute("echo a\\ \\;a"));
-        assertEquals("baabab", m_ctx.execute("echo b${c}${c}b${c}b"));
+        assertEquals("a b", c.execute("echo a b"));
+        assertEquals("a b", c.execute("echo 'a b'"));
+        assertEquals("a b", c.execute("echo \"a b\""));
+        assertEquals("a b", c.execute("echo a  b"));
+        assertEquals("a  b", c.execute("echo 'a  b'"));
+        assertEquals("a  b", c.execute("echo \"a  b\""));
+        assertEquals("a b", c.execute("echo $c  b"));
+        assertEquals("$c  b", c.execute("echo '$c  b'"));
+        assertEquals("a  b", c.execute("echo \"$c  b\""));
+        assertEquals("a b", c.execute("echo ${c}  b"));
+        assertEquals("${c}  b", c.execute("echo '${c}  b'"));
+        assertEquals("a  b", c.execute("echo \"${c}  b\""));
+        assertEquals("aa", c.execute("echo $c$c"));
+        assertEquals("a ;a", c.execute("echo a\\ \\;a"));
+        assertEquals("baabab", c.execute("echo b${c}${c}b${c}b"));
 
-        m_ctx.set("d", "a  b ");
-        assertEquals("a  b ", m_ctx.execute("echo \"$d\""));
+        c.set("d", "a  b ");
+        assertEquals("a  b ", c.execute("echo \"$d\""));
     }
 
     public void testScope() throws Exception
     {
-        m_ctx.addCommand("echo", this);
-        assertEquals("$a", m_ctx.execute("test:echo \\$a"));
-        assertEquals("file://poo", m_ctx.execute("test:echo file://poo"));
+        Context c = new Context();
+        c.addCommand("echo", this);
+        assertEquals("$a", c.execute("test:echo \\$a"));
+        assertEquals("file://poo", c.execute("test:echo file://poo"));
     }
 
     public void testPipe() throws Exception
     {
-        m_ctx.addCommand("echo", this);
-        m_ctx.addCommand("capture", this);
-        m_ctx.addCommand("grep", this);
-        m_ctx.addCommand("echoout", this);
-        m_ctx.execute("myecho = { echoout $args }");
-        assertEquals("def", m_ctx.execute("echo def|grep d.*|capture"));
-        assertEquals("def", m_ctx.execute("echoout def|grep d.*|capture"));
-        assertEquals("def", m_ctx.execute("myecho def|grep d.*|capture"));
+        Context c = new Context();
+        c.addCommand("echo", this);
+        c.addCommand("capture", this);
+        c.addCommand("grep", this);
+        c.addCommand("echoout", this);
+        c.execute("myecho = { echoout $args }");
+        assertEquals("def", c.execute("echo def|grep d.*|capture"));
+        assertEquals("def", c.execute("echoout def|grep d.*|capture"));
+        assertEquals("def", c.execute("myecho def|grep d.*|capture"));
         assertEquals("def",
-            m_ctx.execute("(echoout abc; echoout def; echoout ghi)|grep d.*|capture"));
-        assertEquals("", m_ctx.execute("echoout def; echoout ghi | grep d.* | capture"));
-        assertEquals("hello world", m_ctx.execute("echo hello world|capture"));
+            c.execute("(echoout abc; echoout def; echoout ghi)|grep d.*|capture"));
+        assertEquals("", c.execute("echoout def; echoout ghi | grep d.* | capture"));
+        assertEquals("hello world", c.execute("echo hello world|capture"));
         assertEquals("defghi",
-            m_ctx.execute("(echoout abc; echoout def; echoout ghi)|grep 'def|ghi'|capture"));
+            c.execute("(echoout abc; echoout def; echoout ghi)|grep 'def|ghi'|capture"));
     }
 
     public void testAssignment() throws Exception
     {
-        m_ctx.addCommand("echo", this);
-        m_ctx.addCommand("grep", this);
-        assertEquals("a", m_ctx.execute("a = a; echo ${$a}"));
+        Context c = new Context();
+        c.addCommand("echo", this);
+        c.addCommand("grep", this);
+        assertEquals("a", c.execute("a = a; echo ${$a}"));
 
-        assertEquals("hello", m_ctx.execute("echo hello"));
-        assertEquals("hello", m_ctx.execute("a = (echo hello)"));
-        //assertEquals("a", m_ctx.execute("a = a; echo $(echo a)")); // #p2 - no eval in var expansion
-        assertEquals("3", m_ctx.execute("a=3; echo $a"));
-        assertEquals("3", m_ctx.execute("a = 3; echo $a"));
-        assertEquals("a", m_ctx.execute("a = a; echo ${$a}"));
+        assertEquals("hello", c.execute("echo hello"));
+        assertEquals("hello", c.execute("a = (echo hello)"));
+      //assertEquals("a", c.execute("a = a; echo $(echo a)")); // #p2 - no eval in var expansion
+        assertEquals("3", c.execute("a=3; echo $a"));
+        assertEquals("3", c.execute("a = 3; echo $a"));
+        assertEquals("a", c.execute("a = a; echo ${$a}"));
     }
 
     public void testComment() throws Exception
     {
-        m_ctx.addCommand("echo", this);
-        assertEquals("1", m_ctx.execute("echo 1 // hello"));
+        Context c = new Context();
+        c.addCommand("echo", this);
+        assertEquals("1", c.execute("echo 1 // hello"));
     }
 
     public void testClosure() throws Exception
     {
-        m_ctx.addCommand("echo", this);
-        m_ctx.addCommand("capture", this);
+        Context c = new Context();
+        c.addCommand("echo", this);
+        c.addCommand("capture", this);
 
-        assertEquals("a", m_ctx.execute("e = { echo $1 } ; e a   b"));
-        assertEquals("b", m_ctx.execute("e = { echo $2 } ; e a   b"));
-        assertEquals("b", m_ctx.execute("e = { eval $args } ; e echo  b"));
-        assertEquals("ca b", m_ctx.execute("e = { echo c$args } ; e a  b"));
-        assertEquals("c a b", m_ctx.execute("e = { echo c $args } ; e a  b"));
-        assertEquals("ca  b", m_ctx.execute("e = { echo c$args } ; e 'a  b'"));
+        assertEquals("a", c.execute("e = { echo $1 } ; e a   b"));
+        assertEquals("b", c.execute("e = { echo $2 } ; e a   b"));
+        assertEquals("b", c.execute("e = { eval $args } ; e echo  b"));
+        assertEquals("ca b", c.execute("e = { echo c$args } ; e a  b"));
+        assertEquals("c a b", c.execute("e = { echo c $args } ; e a  b"));
+        assertEquals("ca  b", c.execute("e = { echo c$args } ; e 'a  b'"));
     }
 
     public void testArray() throws Exception
     {
-        m_ctx.set("echo", this);
+        Context c = new Context();
+        c.set("echo", true);
         assertEquals("http://www.aqute.biz?com=2&biz=1",
-            m_ctx.execute("['http://www.aqute.biz?com=2&biz=1'] get 0"));
-        assertEquals("{a=2, b=3}", m_ctx.execute("[a=2 b=3]").toString());
-        assertEquals(3L, m_ctx.execute("[a=2 b=3] get b"));
-        assertEquals("[3, 4]", m_ctx.execute("[1 2 [3 4] 5 6] get 2").toString());
-        assertEquals(5, m_ctx.execute("[1 2 [3 4] 5 6] size"));
+            c.execute("['http://www.aqute.biz?com=2&biz=1'] get 0"));
+        assertEquals("{a=2, b=3}", c.execute("[a=2 b=3]").toString());
+        assertEquals(3L, c.execute("[a=2 b=3] get b"));
+        assertEquals("[3, 4]", c.execute("[1 2 [3 4] 5 6] get 2").toString());
+        assertEquals(5, c.execute("[1 2 [3 4] 5 6] size"));
     }
 
     public void testParentheses()
     {
         Parser parser = new Parser("(a|b)|(d|f)");
-        List<List<List<Token>>> p = parser.program();
-        assertEquals("a|b", p.get(0).get(0).get(0).toString());
+        Program p = parser.program();
+        assertEquals("a|b", ((Sequence) ((Statement) ((Pipeline) p.tokens().get(0)).tokens().get(0)).tokens().get(0)).program().toString());
 
         parser = new Parser("grep (d.*)|grep (d|f)");
         p = parser.program();
-        assertEquals("d.*", p.get(0).get(0).get(1).toString());
+        assertEquals("d.*", ((Sequence)((Statement) ((Pipeline) p.tokens().get(0)).tokens().get(0)).tokens().get(1)).program().toString());
     }
 
     public void testEcho() throws Exception
     {
-        m_ctx.addCommand("echo", this);
-        m_ctx.execute("echo peter");
+        Context c = new Context();
+        c.addCommand("echo", this);
+        c.execute("echo peter");
     }
 
     public void grep(String match) throws IOException
@@ -205,20 +220,22 @@
 
     public void testVars() throws Exception
     {
-        m_ctx.addCommand("echo", this);
+        Context c = new Context();
+        c.addCommand("echo", this);
 
-        assertEquals("", m_ctx.execute("echo ${very.likely.that.this.does.not.exist}"));
-        assertNotNull(m_ctx.execute("echo ${java.shell.name}"));
-        assertEquals("a", m_ctx.execute("a = a; echo ${a}"));
+        assertEquals("", c.execute("echo ${very.likely.that.this.does.not.exist}"));
+        assertNotNull(c.execute("echo ${java.shell.name}"));
+        assertEquals("a", c.execute("a = a; echo ${a}"));
     }
 
     public void testFunny() throws Exception
     {
-        m_ctx.addCommand("echo", this);
-        assertEquals("a", m_ctx.execute("echo a") + "");
-        assertEquals("a", m_ctx.execute("eval (echo echo) a") + "");
-        //assertEquals("a", m_ctx.execute("((echo echo) echo) (echo a)") + "");
-        assertEquals("3", m_ctx.execute("[a=2 (echo b)=(echo 3)] get b").toString());
+        Context c = new Context();
+        c.addCommand("echo", this);
+        assertEquals("a", c.execute("echo a") + "");
+        assertEquals("a", c.execute("eval (echo echo) a") + "");
+        //assertEquals("a", c.execute("((echo echo) echo) (echo a)") + "");
+        assertEquals("3", c.execute("[a=2 (echo b)=(echo 3)] get b").toString());
     }
 
     public CharSequence echo(Object args[])
@@ -248,21 +265,22 @@
 
     public void testContext() throws Exception
     {
-        m_ctx.addCommand("ls", this);
+        Context c = new Context();
+        c.addCommand("ls", this);
         beentheredonethat = 0;
-        m_ctx.execute("ls");
+        c.execute("ls");
         assertEquals(1, beentheredonethat);
 
         beentheredonethat = 0;
-        m_ctx.execute("ls 10");
+        c.execute("ls 10");
         assertEquals(10, beentheredonethat);
 
         beentheredonethat = 0;
-        m_ctx.execute("ls a b c d e f g h i j");
+        c.execute("ls a b c d e f g h i j");
         assertEquals(10, beentheredonethat);
 
         beentheredonethat = 0;
-        Integer result = (Integer) m_ctx.execute("ls (ls 5)");
+        Integer result = (Integer) c.execute("ls (ls 5)");
         assertEquals(10, beentheredonethat);
         assertEquals((Integer) 5, result);
     }
@@ -293,40 +311,52 @@
 
     public void testProgram()
     {
-        List<List<List<Token>>> x = new Parser("abc def|ghi jkl;mno pqr|stu vwx").program();
-        assertEquals("abc", x.get(0).get(0).get(0).toString());
-        assertEquals("def", x.get(0).get(0).get(1).toString());
-        assertEquals("ghi", x.get(0).get(1).get(0).toString());
-        assertEquals("jkl", x.get(0).get(1).get(1).toString());
-        assertEquals("mno", x.get(1).get(0).get(0).toString());
-        assertEquals("pqr", x.get(1).get(0).get(1).toString());
-        assertEquals("stu", x.get(1).get(1).get(0).toString());
-        assertEquals("vwx", x.get(1).get(1).get(1).toString());
+        Program x = new Parser("abc def|ghi jkl;mno pqr|stu vwx").program();
+        Pipeline p0 = (Pipeline) x.tokens().get(0);
+        Statement s00 = (Statement) p0.tokens().get(0);
+        Statement s01 = (Statement) p0.tokens().get(1);
+        Pipeline p1 = (Pipeline) x.tokens().get(1);
+        Statement s10 = (Statement) p1.tokens().get(0);
+        Statement s11 = (Statement) p1.tokens().get(1);
+        assertEquals("abc", s00.tokens().get(0).toString());
+        assertEquals("def", s00.tokens().get(1).toString());
+        assertEquals("ghi", s01.tokens().get(0).toString());
+        assertEquals("jkl", s01.tokens().get(1).toString());
+        assertEquals("mno", s10.tokens().get(0).toString());
+        assertEquals("pqr", s10.tokens().get(1).toString());
+        assertEquals("stu", s11.tokens().get(0).toString());
+        assertEquals("vwx", s11.tokens().get(1).toString());
     }
 
     public void testStatements()
     {
-        List<List<Token>> x = new Parser("abc def|ghi jkl|mno pqr").program().get(0);
-        assertEquals("abc", x.get(0).get(0).toString());
-        assertEquals("def", x.get(0).get(1).toString());
-        assertEquals("ghi", x.get(1).get(0).toString());
-        assertEquals("jkl", x.get(1).get(1).toString());
-        assertEquals("mno", x.get(2).get(0).toString());
-        assertEquals("pqr", x.get(2).get(1).toString());
+        Program x = new Parser("abc def|ghi jkl|mno pqr").program();
+        Pipeline p0 = (Pipeline) x.tokens().get(0);
+        Statement s00 = (Statement) p0.tokens().get(0);
+        Statement s01 = (Statement) p0.tokens().get(1);
+        Statement s02 = (Statement) p0.tokens().get(2);
+        assertEquals("abc", s00.tokens().get(0).toString());
+        assertEquals("def", s00.tokens().get(1).toString());
+        assertEquals("ghi", s01.tokens().get(0).toString());
+        assertEquals("jkl", s01.tokens().get(1).toString());
+        assertEquals("mno", s02.tokens().get(0).toString());
+        assertEquals("pqr", s02.tokens().get(1).toString());
     }
 
     public void testSimpleValue()
     {
-        List<Token> x = new Parser(
-            "abc def.ghi http://www.osgi.org?abc=&x=1 [1,2,3] {{{{{{{xyz}}}}}}} (immediate) {'{{{{{'} {\\{} 'abc{}'").program().get(0).get(0);
+        Program p = new Parser(
+            "abc def.ghi http://www.osgi.org?abc=&x=1 [1,2,3] {{{{{{{xyz}}}}}}} (immediate) {'{{{{{'} {\\{} 'abc{}'")
+            .program();
+        List<Token> x = ((Statement) p.tokens().get(0)).tokens();
         assertEquals("abc", x.get(0).toString());
         assertEquals("def.ghi", x.get(1).toString());
         assertEquals("http://www.osgi.org?abc=&x=1", x.get(2).toString());
-        assertEquals("1,2,3", x.get(3).toString());
-        assertEquals("{{{{{{xyz}}}}}}", x.get(4).toString());
-        assertEquals("immediate", x.get(5).toString());
-        assertEquals("'{{{{{'", x.get(6).toString());
-        assertEquals("\\{", x.get(7).toString());
+        assertEquals("[1,2,3]", x.get(3).toString());
+        assertEquals("{{{{{{{xyz}}}}}}}", x.get(4).toString());
+        assertEquals("(immediate)", x.get(5).toString());
+        assertEquals("{'{{{{{'}", x.get(6).toString());
+        assertEquals("{\\{}", x.get(7).toString());
         assertEquals("'abc{}'", x.get(8).toString());
     }
 
diff --git a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser2.java b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser2.java
index 4df5dab..1966d43 100644
--- a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser2.java
+++ b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser2.java
@@ -24,19 +24,20 @@
  * Test features of the new parser/tokenizer, many of which are not supported
  * by the original parser.
  */
-public class TestParser2 extends BaseTestCase
+public class TestParser2 extends AbstractParserTest
 {
     public void testComment() throws Exception
     {
-        m_ctx.addCommand("echo", this);
+        Context c = new Context();
+        c.addCommand("echo", this);
 
-        assertEquals("file://wibble#tag", m_ctx.execute("echo file://wibble#tag"));
-        assertEquals("file:", m_ctx.execute("echo file: //wibble#tag"));
+        assertEquals("file://wibble#tag", c.execute("echo file://wibble#tag"));
+        assertEquals("file:", c.execute("echo file: //wibble#tag"));
 
-        assertEquals("PWD/*.java", m_ctx.execute("echo PWD/*.java"));
+        assertEquals("PWD/*.java", c.execute("echo PWD/*.java"));
         try
         {
-            m_ctx.execute("echo PWD /*.java");
+            c.execute("echo PWD /*.java");
             fail("expected EOFException");
         }
         catch (EOFException e)
@@ -44,42 +45,44 @@
             // expected
         }
 
-        assertEquals("ok", m_ctx.execute("// can't quote\necho ok\n"));
+        assertEquals("ok", c.execute("// can't quote\necho ok\n"));
 
         // quote in comment in closure
-        assertEquals("ok", m_ctx.execute("x = { // can't quote\necho ok\n}; x"));
-        assertEquals("ok", m_ctx.execute("x = {\n// can't quote\necho ok\n}; x"));
-        assertEquals("ok", m_ctx.execute("x = {// can't quote\necho ok\n}; x"));
+        assertEquals("ok", c.execute("x = { // can't quote\necho ok\n}; x"));
+        assertEquals("ok", c.execute("x = {\n// can't quote\necho ok\n}; x"));
+        assertEquals("ok", c.execute("x = {// can't quote\necho ok\n}; x"));
     }
 
     public void testCoercion() throws Exception
     {
-        m_ctx.addCommand("echo", this);
+        Context c = new Context();
+        c.addCommand("echo", this);
         // FELIX-2432
-        assertEquals("null x", m_ctx.execute("echo $expandsToNull x"));
+        assertEquals("null x", c.execute("echo $expandsToNull x"));
     }
 
     public void testStringExecution() throws Exception
     {
-        m_ctx.addCommand("echo", this);
-        m_ctx.addCommand("new", this);
+        Context c = new Context();
+        c.addCommand("echo", this);
+        c.addCommand("new", this);
         
         // FELIX-2433
-        assertEquals("helloworld", m_ctx.execute("echo \"$(echo hello)world\""));
+        assertEquals("helloworld", c.execute("echo \"$(echo hello)world\""));
         
          // FELIX-1473 - allow method calls on String objects
-        assertEquals("hello", m_ctx.execute("cmd = echo; eval $cmd hello"));
-        assertEquals(4, m_ctx.execute("'four' length"));
+        assertEquals("hello", c.execute("cmd = echo; eval $cmd hello"));
+        assertEquals(4, c.execute("'four' length"));
         try {
-            m_ctx.execute("four length");
+            c.execute("four length");
             fail("expected: command not found: four");
         } catch (IllegalArgumentException e) {
         }
         
         // check CharSequence types are preserved
-        Object b = m_ctx.execute("b = new java.lang.StringBuilder");
+        Object b = c.execute("b = new java.lang.StringBuilder");
         assertTrue(b instanceof StringBuilder);
-        assertEquals(b, m_ctx.execute("c = $b"));
+        assertEquals(b, c.execute("c = $b"));
     }
 
     public CharSequence echo(Object args[])
diff --git a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser3.java b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser3.java
index 2bfdbf8..4021fdb 100644
--- a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser3.java
+++ b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser3.java
@@ -22,25 +22,19 @@
  * Test features of the new parser/tokenizer, many of which are not supported
  * by the original parser.
  */
-public class TestParser3 extends BaseTestCase
+public class TestParser3 extends AbstractParserTest
 {
     public void testArithmetic() throws Exception
     {
-        m_ctx.addCommand("echo", this);
+        Context c = new Context();
+        c.addCommand("echo", this);
 
-        try
-        {
-            assertEquals("10d", m_ctx.execute("echo %(2*(3+2))d"));
-            assertEquals(3l, m_ctx.execute("%(1+2)"));
+        assertEquals("10d", c.execute("echo %(2*(3+2))d"));
+        assertEquals(3l, c.execute("%(1+2)"));
 
-            m_ctx.set("a", 2l);
-            assertEquals(3l, m_ctx.execute("%(a+=1)"));
-            assertEquals(3l, m_ctx.get("a"));
-        }
-        finally
-        {
-            m_ctx.stop();
-        }
+        c.set("a", 2l);
+        assertEquals(3l, c.execute("%(a+=1)"));
+        assertEquals(3l, c.get("a"));
     }
 
     public CharSequence echo(Object args[])
diff --git a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestTokenizer.java b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestTokenizer.java
index 434d280..c7f17f7 100644
--- a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestTokenizer.java
+++ b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestTokenizer.java
@@ -18,6 +18,9 @@
  */
 package org.apache.felix.gogo.runtime;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -28,11 +31,15 @@
 import java.util.HashMap;
 import java.util.Map;
 
-import org.apache.felix.gogo.runtime.Tokenizer.Type;
+import junit.framework.TestCase;
+
+import org.apache.felix.gogo.runtime.threadio.ThreadIOImpl;
+import org.junit.Ignore;
+import org.junit.Test;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 
-public class TestTokenizer extends BaseTestCase
+public class TestTokenizer
 {
     private final Map<String, Object> vars = new HashMap<String, Object>();
     private final Evaluate evaluate;
@@ -55,9 +62,14 @@
             {
                 return vars.put(key, value);
             }
+
+            public Object expr(Token t) {
+                throw new UnsupportedOperationException("expr not implemented.");
+            }
         };
     }
 
+    @Test
     public void testHello() throws Exception
     {
         testHello("hello world\n");
@@ -91,14 +103,13 @@
     private void testHello(CharSequence text) throws Exception
     {
         Tokenizer t = new Tokenizer(text);
-        assertEquals(Type.WORD, t.next());
-        assertEquals("hello", t.value().toString());
-        assertEquals(Type.WORD, t.next());
-        assertEquals("world", t.value().toString());
-        assertEquals(Type.NEWLINE, t.next());
-        assertEquals(Type.EOT, t.next());
+        assertEquals("hello", t.next().toString());
+        assertEquals("world", t.next().toString());
+        assertEquals("\n", t.next().toString());
+        assertNull(t.next());
     }
-
+    
+    @Test
     public void testString() throws Exception
     {
         testString("'single $quote' \"double $quote\"\n");
@@ -108,45 +119,52 @@
     private void testString(CharSequence text) throws Exception
     {
         Tokenizer t = new Tokenizer(text);
-        assertEquals(Type.WORD, t.next());
-        assertEquals("'single $quote'", t.value().toString());
-        assertEquals(Type.WORD, t.next());
-        assertEquals("\"double $quote\"", t.value().toString());
-        assertEquals(Type.NEWLINE, t.next());
-        assertEquals(Type.EOT, t.next());
+        assertEquals("'single $quote'", t.next().toString());
+        assertEquals("\"double $quote\"", t.next().toString());
+        assertEquals("\n", t.next().toString());
+        assertNull(t.next());
     }
 
+    @Test
     public void testClosure() throws Exception
     {
         testClosure2("x = { echo '}' $args //comment's\n}\n");
         testClosure2("x={ echo '}' $args //comment's\n}\n");
-        assertEquals(Type.CLOSURE, token1("{ echo \\{ $args \n}"));
-        assertEquals(Type.CLOSURE, token1("{ echo \\} $args \n}"));
+        token1("{ echo \\{ $args \n}");
+        token1("{ echo \\} $args \n}");
     }
 
-    /*
-     * x = {echo $args};
-     */
+    //
+    // x = {echo $args};
+    //
     private void testClosure2(CharSequence text) throws Exception
     {
         Tokenizer t = new Tokenizer(text);
-        assertEquals(Type.WORD, t.next());
-        assertEquals("x", t.value().toString());
-        assertEquals(Type.ASSIGN, t.next());
-        assertEquals(Type.CLOSURE, t.next());
-        assertEquals(" echo '}' $args //comment's\n", t.value().toString());
-        assertEquals(Type.NEWLINE, t.next());
-        assertEquals(Type.EOT, t.next());
+        assertEquals("x", t.next().toString());
+        assertEquals("=", t.next().toString());
+        assertEquals("{", t.next().toString());
+        assertEquals("echo", t.next().toString());
+        assertEquals("'}'", t.next().toString());
+        assertEquals("$args", t.next().toString());
+        assertEquals("\n", t.next().toString());
+        assertEquals("}", t.next().toString());
+        assertEquals("\n", t.next().toString());
+        assertEquals(null, t.next());
     }
 
-    private Type token1(CharSequence text) throws Exception
+    private void token1(CharSequence text) throws Exception
     {
         Tokenizer t = new Tokenizer(text);
-        Type type = t.next();
-        assertEquals(Type.EOT, t.next());
-        return type;
+        assertEquals("{", t.next().toString());
+        assertEquals("echo", t.next().toString());
+        t.next();
+        assertEquals("$args", t.next().toString());
+        assertEquals("\n", t.next().toString());
+        assertEquals("}", t.next().toString());
+        assertNull(t.next());
     }
 
+    @Test
     public void testExpand() throws Exception
     {
         final URI home = new URI("/home/derek");
@@ -160,7 +178,7 @@
         vars.put(user, "Derek Baum");
 
         // quote removal
-        assertEquals("hello", expand("hello"));
+        assertEquals("hello", expand("hello").toString());
         assertEquals("hello", expand("'hello'"));
         assertEquals("\"hello\"", expand("'\"hello\"'"));
         assertEquals("hello", expand("\"hello\""));
@@ -266,33 +284,46 @@
 
     private Object expand(CharSequence word) throws Exception
     {
-        return Tokenizer.expand(word, evaluate);
+        return Expander.expand(word, evaluate);
     }
 
+    @Test
     public void testParser() throws Exception
     {
         new Parser("// comment\n" + "a=\"who's there?\"; ps -ef;\n" + "ls | \n grep y\n").program();
         String p1 = "a=1 \\$b=2 c={closure}\n";
         new Parser(p1).program();
-        new Parser(new Token(Type.ARRAY, p1, (short) 0, (short) 0)).program();
+        new Parser("[" + p1 + "]").program();
     }
 
-    /**
-     * FELIX-4679 / FELIX-4671. 
-     */
+    //
+    // FELIX-4679 / FELIX-4671.
+    //
+    @Test
     public void testScriptFelix4679() throws Exception
     {
         String script = "addcommand system (((${.context} bundles) 0) loadclass java.lang.System)";
 
-        BundleContext bc = createMockContext();
+        ThreadIOImpl tio = new ThreadIOImpl();
+        tio.start();
 
-        m_ctx.addCommand("gogo", m_ctx, "addcommand");
-        m_ctx.addConstant(".context", bc);
+        try
+        {
+            BundleContext bc = createMockContext();
 
-        CommandSessionImpl session = new CommandSessionImpl(m_ctx, new ByteArrayInputStream(script.getBytes()), System.out, System.err);
+            CommandProcessorImpl processor = new CommandProcessorImpl(tio);
+            processor.addCommand("gogo", processor, "addcommand");
+            processor.addConstant(".context", bc);
 
-        Closure c = new Closure(session, null, script);
-        assertNull(c.execute(session, null));
+            CommandSessionImpl session = new CommandSessionImpl(processor, new ByteArrayInputStream(script.getBytes()), System.out, System.err);
+
+            Closure c = new Closure(session, null, script);
+            assertNull(c.execute(session, null));
+        }
+        finally
+        {
+            tio.stop();
+        }
     }
 
     private BundleContext createMockContext() throws ClassNotFoundException
diff --git a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/threadio/TestThreadIO.java b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/threadio/TestThreadIO.java
index 2cc60df..32ea917 100644
--- a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/threadio/TestThreadIO.java
+++ b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/threadio/TestThreadIO.java
@@ -38,7 +38,6 @@
     {
         ThreadIOImpl tio = new ThreadIOImpl();
         tio.start();
-
         List<ByteArrayOutputStream> list = new ArrayList<ByteArrayOutputStream>();
         for (int i = 0; i < 10; i++)
         {
@@ -67,7 +66,6 @@
     {
         ThreadIOImpl tio = new ThreadIOImpl();
         tio.start();
-
         System.out.println("Hello World");
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         ByteArrayOutputStream err = new ByteArrayOutputStream();