You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by gn...@apache.org on 2016/03/21 17:52:52 UTC

svn commit: r1735994 [1/2] - in /felix/trunk/gogo/runtime: ./ src/main/java/org/apache/felix/gogo/runtime/ src/main/java/org/apache/felix/service/command/ src/test/java/org/apache/felix/gogo/runtime/ src/test/java/org/apache/felix/gogo/runtime/threadio/

Author: gnodet
Date: Mon Mar 21 16:52:52 2016
New Revision: 1735994

URL: http://svn.apache.org/viewvc?rev=1735994&view=rev
Log:
Improve gogo parser to fully parse the command line instead of skipping inner constructs such as arrays or closures, and report missing terminators

Added:
    felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/BaseTokenizer.java
    felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Expander.java
    felix/trunk/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/AbstractParserTest.java
      - copied, changed from r1735204, felix/trunk/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/BaseTestCase.java
Removed:
    felix/trunk/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/BaseTestCase.java
Modified:
    felix/trunk/gogo/runtime/pom.xml
    felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Closure.java
    felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandProcessorImpl.java
    felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandSessionImpl.java
    felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/EOFError.java
    felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Evaluate.java
    felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Parser.java
    felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Pipe.java
    felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Reflective.java
    felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/SyntaxError.java
    felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Token.java
    felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Tokenizer.java
    felix/trunk/gogo/runtime/src/main/java/org/apache/felix/service/command/CommandSession.java
    felix/trunk/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/Context.java
    felix/trunk/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestCoercion.java
    felix/trunk/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser.java
    felix/trunk/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser2.java
    felix/trunk/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser3.java
    felix/trunk/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestTokenizer.java
    felix/trunk/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/threadio/TestThreadIO.java

Modified: felix/trunk/gogo/runtime/pom.xml
URL: http://svn.apache.org/viewvc/felix/trunk/gogo/runtime/pom.xml?rev=1735994&r1=1735993&r2=1735994&view=diff
==============================================================================
--- felix/trunk/gogo/runtime/pom.xml (original)
+++ felix/trunk/gogo/runtime/pom.xml Mon Mar 21 16:52:52 2016
@@ -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>

Added: felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/BaseTokenizer.java
URL: http://svn.apache.org/viewvc/felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/BaseTokenizer.java?rev=1735994&view=auto
==============================================================================
--- felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/BaseTokenizer.java (added)
+++ felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/BaseTokenizer.java Mon Mar 21 16:52:52 2016
@@ -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();
+        }
+    }
+
+}

Modified: felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Closure.java
URL: http://svn.apache.org/viewvc/felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Closure.java?rev=1735994&r1=1735993&r2=1735994&view=diff
==============================================================================
--- felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Closure.java (original)
+++ felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Closure.java Mon Mar 21 16:52:52 2016
@@ -21,17 +21,24 @@ package org.apache.felix.gogo.runtime;
 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 @@ public class Closure implements Function
     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 @@ public class Closure implements Function
         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 @@ public class Closure implements Function
         try
         {
             location.remove();
-            session.variables.remove(LOCATION);
+            session.put(LOCATION, null);
             return execute(values);
         }
         catch (Exception e)
@@ -152,15 +175,14 @@ public class Closure implements Function
         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 @@ public class Closure implements Function
                 }
                 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 @@ public class Closure implements Function
                 }
             }
 
-            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 @@ public class Closure implements Function
                     session.put("pipe-exception", pipe.exception);
                 }
             }
-
+            last = pipes.get(pipes.size() - 1);
             if (last.exception != null)
             {
                 Pipe.reset(mark);
@@ -231,6 +251,24 @@ public class Closure implements Function
         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 @@ public class Closure implements Function
             }
             catch (NumberFormatException e)
             {
+                // Ignore
             }
         }
         return v;
@@ -263,44 +302,43 @@ public class Closure implements Function
 
     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 @@ public class Closure implements Function
      *    <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 @@ public class Closure implements Function
         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<Token> tokens = statement.tokens();
+        if (tokens.isEmpty())
+        {
+            return null;
+        }
+
         List<Object> values = new ArrayList<Object>();
-        errTok = statement.get(0);
+        errTok = tokens.get(0);
 
-        if ((statement.size() > 3) && Type.ASSIGN.equals(statement.get(1).type))
+        if ((tokens.size() > 3) && Token.eq("=", tokens.get(1)))
         {
-            errTok2 = statement.get(2);
+            errTok2 = tokens.get(2);
         }
 
-        for (Token t : statement)
+        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 @@ public class Closure implements Function
         }
 
         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 @@ public class Closure implements Function
 
             if (values.size() == 0)
             {
-                return session.variables.remove(scmd);
+                return session.put(scmd, null);
             }
 
             if (values.size() == 1)
@@ -389,12 +426,12 @@ public class Closure implements Function
                 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 @@ public class Closure implements Function
 
         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 @@ public class Closure implements Function
 
     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 @@ public class Closure implements Function
                         }
                         finally
                         {
-                            session.variables.remove(DEFAULT_LOCK);
+                            session.put(DEFAULT_LOCK, null);
                         }
                     }
                 }
@@ -533,22 +570,21 @@ public class Closure implements Function
 
     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 @@ public class Closure implements Function
                 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 @@ public class Closure implements Function
                 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 class Closure implements Function
 
     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]+", " ");
     }
 
     /**

Modified: felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandProcessorImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandProcessorImpl.java?rev=1735994&r1=1735993&r2=1735994&view=diff
==============================================================================
--- felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandProcessorImpl.java (original)
+++ felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandProcessorImpl.java Mon Mar 21 16:52:52 2016
@@ -36,6 +36,7 @@ import org.apache.felix.gogo.api.Command
 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 @@ public class CommandProcessorImpl implem
         }
     }
 
-    void removeSession(CommandSessionImpl session)
+    void closeSession(CommandSessionImpl session)
     {
         synchronized (sessions)
         {
@@ -179,13 +180,15 @@ public class CommandProcessorImpl implem
         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 @@ public class CommandProcessorImpl implem
         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 @@ public class CommandProcessorImpl implem
             }
             catch (Exception e)
             {
-                e.printStackTrace();
+                // Ignore
+                e.getCause();
             }
         }
+
         return null;
     }
 

Modified: felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandSessionImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandSessionImpl.java?rev=1735994&r1=1735993&r2=1735994&view=diff
==============================================================================
--- felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandSessionImpl.java (original)
+++ felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandSessionImpl.java Mon Mar 21 16:52:52 2016
@@ -24,6 +24,7 @@ package org.apache.felix.gogo.runtime;
 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.Enumeration;
 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 class CommandSessionImpl implemen
     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 @@ public class CommandSessionImpl implemen
         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 @@ public class CommandSessionImpl implemen
             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 @@ public class CommandSessionImpl implemen
         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)
+        {
+            return variables.put(name, value);
+        }
+        else
         {
-            variables.put(name, value);
+            return variables.remove(name);
         }
     }
 
@@ -343,52 +367,48 @@ public class CommandSessionImpl implemen
     {
         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 (!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)
+                String name = m.getName();
+                if (m.getName().startsWith("get") && !m.getName().equals("getClass") && m.getParameterTypes().length == 0 && Modifier.isPublic(m.getModifiers()))
                 {
-                    // 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)
+        {
+            return (StringBuilder) f.out();
+        }
+        else
         {
-            f.close();
+            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 @@ public class CommandSessionImpl implemen
         }
         catch (Exception e)
         {
-            return "<can not format " + result + ">:" + e;
+            return "<can not format " + result + ":" + e;
         }
     }
 
@@ -407,4 +427,5 @@ public class CommandSessionImpl implemen
     {
         return processor.expr(this, expr);
     }
+
 }

Modified: felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/EOFError.java
URL: http://svn.apache.org/viewvc/felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/EOFError.java?rev=1735994&r1=1735993&r2=1735994&view=diff
==============================================================================
--- felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/EOFError.java (original)
+++ felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/EOFError.java Mon Mar 21 16:52:52 2016
@@ -21,9 +21,24 @@ package org.apache.felix.gogo.runtime;
 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;
+    }
+
 }

Modified: felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Evaluate.java
URL: http://svn.apache.org/viewvc/felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Evaluate.java?rev=1735994&r1=1735993&r2=1735994&view=diff
==============================================================================
--- felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Evaluate.java (original)
+++ felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Evaluate.java Mon Mar 21 16:52:52 2016
@@ -25,4 +25,6 @@ public interface Evaluate
     Object get(String key);
     
     Object put(String key, Object value);
+
+    Object expr(Token t);
 }

Added: felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Expander.java
URL: http://svn.apache.org/viewvc/felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Expander.java?rev=1735994&view=auto
==============================================================================
--- felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Expander.java (added)
+++ felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Expander.java Mon Mar 21 16:52:52 2016
@@ -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);
+    }
+
+}

Modified: felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Parser.java
URL: http://svn.apache.org/viewvc/felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Parser.java?rev=1735994&r1=1735993&r2=1735994&view=diff
==============================================================================
--- felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Parser.java (original)
+++ felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Parser.java Mon Mar 21 16:52:52 2016
@@ -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;
         }
 
-        if (tz.next() != Type.EOT)
+        public List<Executable> tokens()
         {
-            throw new RuntimeException("Program has trailing text: " + tz.value());
+            return tokens;
         }
-        return program;
     }
 
-    private List<List<Token>> pipeline()
+    /**
+     * token1 | token2 | ...
+     */
+    public static class Pipeline extends Executable
     {
-        List<List<Token>> pipeline = new ArrayList<List<Token>>();
+        private final List<Executable> tokens;
 
-        while (true)
+        public Pipeline(Token cs, List<Executable> tokens)
         {
-            pipeline.add(command());
-            switch (tz.type())
-            {
-                case PIPE:
-                    if (tz.next() == Type.EOT)
-                    {
-                        Token t = tz.token();
-                        throw new EOFError(t.line, t.column, "unexpected EOT after pipe '|'");
-                    }
-                    break;
+            super(cs);
+            this.tokens = tokens;
+        }
 
-                default:
-                    return pipeline;
-            }
+        public List<Executable> tokens()
+        {
+            return tokens;
         }
+
     }
 
-    private List<Token> command()
+    /**
+     * ( program )
+     */
+    public static class Sequence extends Executable
     {
-        List<Token> command = new ArrayList<Token>();
+        private final Program program;
 
-        while (true)
+        public Sequence(Token cs, Program program)
         {
-            Token t = tz.token();
+            super(cs);
+            this.program = program;
+        }
 
-            switch (t.type)
-            {
-                case WORD:
-                case CLOSURE:
-                case EXECUTION:
-                case ARRAY:
-                case ASSIGN:
-                case EXPR:
-                    break;
+        public Program program()
+        {
+            return program;
+        }
+    }
 
-                default:
-                    throw new SyntaxError(t.line, t.column, "unexpected token: " + t.type);
-            }
+    /**
+     * { program }
+     */
+    public static class Closure extends Token
+    {
+        private final Program program;
 
-            command.add(t);
+        public Closure(Token cs, Program program)
+        {
+            super(cs);
+            this.program = program;
+        }
 
-            switch (tz.next())
-            {
-                case PIPE:
-                case SEMICOLON:
-                case NEWLINE:
-                case EOT:
-                    return command;
+        public Program program()
+        {
+            return program;
+        }
+    }
 
-                default:
-                    break;
-            }
+    /**
+     * [ 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;
         }
     }
 
-    public void array(List<Token> list, Map<Token, Token> map) throws Exception
+    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)
     {
-        Token lt = null;
-        boolean isMap = false;
+        this.tz = new Tokenizer(line);
+    }
+
+    public List<Token> tokens() {
+        return Collections.unmodifiableList(tokens);
+    }
 
-        while (tz.next() != Type.EOT)
+    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);
+            }
+        });
+        return Collections.unmodifiableList(statements);
+    }
+
+    public Program program()
+    {
+        List<Executable> tokens = new ArrayList<Executable>();
+        List<Executable> pipes = null;
+        int start = tz.index - 1;
+        while (true)
         {
-            if (isMap)
+            Executable ex;
+            Token t = next();
+            if (t == null)
+            {
+                if (pipes != null)
+                {
+                    throw new EOFError(tz.line, tz.column, "unexpected EOT while looking for a statement after |", getMissing("pipe"), "0");
+                }
+                else
+                {
+                    return new Program(whole(tokens, start), tokens);
+                }
+            }
+            if (Token.eq("}", t) || Token.eq(")", t))
             {
-                Token key = lt;
-                lt = null;
-                if (null == key)
+                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
+            {
+                push(t);
+                ex = statement();
+            }
+            t = next();
+            if (t == null || Token.eq(";", t) || Token.eq("\n", t))
+            {
+                if (pipes != null)
+                {
+                    pipes.add(ex);
+                    tokens.add(new Pipeline(whole(pipes, start), pipes));
+                    pipes = null;
+                }
+                else
                 {
-                    key = tz.token();
+                    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);
+            }
+        }
+    }
 
-                    if (tz.next() != Type.ASSIGN)
-                    {
-                        Token t = tz.token();
-                        throw new SyntaxError(t.line, t.column,
-                            "map expected '=', found: " + t);
-                    }
+    protected void push(Token t) {
+        tz.push(t);
+    }
 
-                    tz.next();
-                }
+    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);
+    }
 
-                Token k = (list.isEmpty() ? key : list.remove(0));
-                Token v = tz.token();
-                map.put(k, v);
+    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
             {
-                switch (tz.type())
+                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))
                 {
-                    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);
+                    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());
     }
 
 }

Modified: felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Pipe.java
URL: http://svn.apache.org/viewvc/felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Pipe.java?rev=1735994&r1=1735993&r2=1735994&view=diff
==============================================================================
--- felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Pipe.java (original)
+++ felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Pipe.java Mon Mar 21 16:52:52 2016
@@ -24,8 +24,8 @@ import java.io.PipedInputStream;
 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 @@ public class Pipe extends Thread
     Closure closure;
     Exception exception;
     Object result;
-    List<Token> statement;
+    Executable executable;
 
     public static Object[] mark()
     {
@@ -55,11 +55,11 @@ public class Pipe extends Thread
         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 class Pipe extends Thread
 
     public String toString()
     {
-        return "pipe<" + statement + "> out=" + out;
+        return "pipe<" + executable + "> out=" + out;
     }
 
     public void setIn(InputStream in)
@@ -105,7 +105,7 @@ public class Pipe extends Thread
 
         try
         {
-            result = closure.executeStatement(statement);
+            result = closure.execute(executable);
             if (result != null && pout != null)
             {
                 if (!Boolean.FALSE.equals(closure.session().get(".FormatPipe")))

Modified: felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Reflective.java
URL: http://svn.apache.org/viewvc/felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Reflective.java?rev=1735994&r1=1735993&r2=1735994&view=diff
==============================================================================
--- felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Reflective.java (original)
+++ felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Reflective.java Mon Mar 21 16:52:52 2016
@@ -346,7 +346,7 @@ public final class Reflective
      * 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 @@ public final class Reflective
         // 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 @@ public final class Reflective
             return string;
         }
 
+        if (type.isEnum())
+        {
+            for (Object o : type.getEnumConstants())
+            {
+                if (o.toString().equalsIgnoreCase(string))
+                {
+                    return o;
+                }
+            }
+        }
+
         if (type.isPrimitive())
         {
             type = primitiveToObject(type);

Modified: felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/SyntaxError.java
URL: http://svn.apache.org/viewvc/felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/SyntaxError.java?rev=1735994&r1=1735993&r2=1735994&view=diff
==============================================================================
--- felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/SyntaxError.java (original)
+++ felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/SyntaxError.java Mon Mar 21 16:52:52 2016
@@ -23,12 +23,19 @@ public class SyntaxError extends Runtime
     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 @@ public class SyntaxError extends Runtime
     {
         return line;
     }
+
+    public Token statement() {
+        return statement;
+    }
+
 }

Modified: felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Token.java
URL: http://svn.apache.org/viewvc/felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Token.java?rev=1735994&r1=1735993&r2=1735994&view=diff
==============================================================================
--- felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Token.java (original)
+++ felix/trunk/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Token.java Mon Mar 21 16:52:52 2016
@@ -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)
-    {
-        this.type = type;
-        this.value = value;
-        this.line = line;
-        this.column = 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)
+    {
+        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()
-    {
-        switch (type)
-        {
-            case WORD:
-                return value.toString();
-                
-            case CLOSURE:
-                return "{" + value + "}";
-                
-            case EXECUTION:
-                return "(" + value + ")";
-                
-            case ARRAY:
-                return "[" + value + "]";
-                
-            default:
-                return type.toString();
+
+    public Token subSequence(int start, int end)
+    {
+        int line = this.line;
+        int col = this.column;
+        for (int i = this.start; i < this.start + start; i++)
+        {
+            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;
     }
+
 }