Add file name generation, fix quote behaviors

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1736017 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Posix.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Posix.java
index 070bb40..ca89217 100644
--- a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Posix.java
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Posix.java
@@ -30,17 +30,12 @@
 import java.io.OutputStream;
 import java.io.PrintStream;
 import java.io.Reader;
-import java.nio.file.FileVisitOption;
-import java.nio.file.FileVisitResult;
-import java.nio.file.FileVisitor;
 import java.nio.file.Files;
 import java.nio.file.LinkOption;
 import java.nio.file.Path;
-import java.nio.file.PathMatcher;
 import java.nio.file.StandardWatchEventKinds;
 import java.nio.file.WatchKey;
 import java.nio.file.WatchService;
-import java.nio.file.attribute.BasicFileAttributes;
 import java.nio.file.attribute.FileTime;
 import java.nio.file.attribute.PosixFilePermission;
 import java.nio.file.attribute.PosixFilePermissions;
@@ -126,7 +121,6 @@
             throw new IllegalArgumentException();
         }
         try {
-            argv = expand(session, argv);
             run(session, argv);
         } catch (IllegalArgumentException e) {
             System.err.println(e.getMessage());
@@ -161,81 +155,6 @@
         return o != null ? o.toString() : null;
     }
 
-    protected String[] expand(CommandSession session, String[] argv) throws IOException {
-        String rsv = "*(|<\\[?";
-        String reserved = "(?<!\\\\)[" + rsv + "]";
-        List<String> params = new ArrayList<>();
-        for (String arg : argv) {
-            if (arg.matches(".*" + reserved + ".*")) {
-                String org = arg;
-                List<String> expanded = new ArrayList<>();
-                Path currentDir = session.currentDir();
-                Path dir;
-                String pfx = arg.replaceFirst(reserved + ".*", "");
-                String prefix;
-                if (pfx.indexOf('/') >= 0) {
-                    pfx = pfx.substring(0, pfx.lastIndexOf('/'));
-                    arg = arg.substring(pfx.length() + 1);
-                    dir = currentDir.resolve(pfx).normalize();
-                    prefix = pfx + "/";
-                } else {
-                    dir = currentDir;
-                    prefix = "";
-                }
-                PathMatcher matcher = dir.getFileSystem().getPathMatcher("glob:" + arg);
-                Files.walkFileTree(dir,
-                        EnumSet.of(FileVisitOption.FOLLOW_LINKS),
-                        Integer.MAX_VALUE,
-                        new FileVisitor<Path>() {
-                            @Override
-                            public FileVisitResult preVisitDirectory(Path file, BasicFileAttributes attrs) throws IOException {
-                                if (file.equals(dir)) {
-                                    return FileVisitResult.CONTINUE;
-                                }
-                                if (Files.isHidden(file)) {
-                                    return FileVisitResult.SKIP_SUBTREE;
-                                }
-                                Path r = dir.relativize(file);
-                                if (matcher.matches(r)) {
-                                    expanded.add(prefix + r.toString());
-                                }
-                                return FileVisitResult.CONTINUE;
-                            }
-
-                            @Override
-                            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
-                                if (!Files.isHidden(file)) {
-                                    Path r = dir.relativize(file);
-                                    if (matcher.matches(r)) {
-                                        expanded.add(prefix + r.toString());
-                                    }
-                                }
-                                return FileVisitResult.CONTINUE;
-                            }
-
-                            @Override
-                            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
-                                return FileVisitResult.CONTINUE;
-                            }
-
-                            @Override
-                            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
-                                return FileVisitResult.CONTINUE;
-                            }
-                        });
-                Collections.sort(expanded);
-                if (expanded.isEmpty()) {
-                    throw new IOException("no matches found: " + org);
-                }
-                params.addAll(expanded);
-            } else {
-                arg = arg.replaceAll("(\\\\)([" + rsv + "])", "$2");
-                params.add(arg);
-            }
-        }
-        return params.toArray(new String[params.size()]);
-    }
-
     protected Object run(CommandSession session, String[] argv) throws Exception {
         switch (argv[0]) {
             case "cat":
@@ -1432,7 +1351,72 @@
             for (String arg : args) {
                 if (buf.length() > 0)
                     buf.append(' ');
-                buf.append(arg);
+                for (int i = 0; i < arg.length(); i++) {
+                    int c = arg.charAt(i);
+                    int ch;
+                    if (c == '\\') {
+                        c = i < arg.length() - 1 ? arg.charAt(++i) : '\\';
+                        switch (c) {
+                            case 'a':
+                                buf.append('\u0007');
+                                break;
+                            case 'n':
+                                buf.append('\n');
+                                break;
+                            case 't':
+                                buf.append('\t');
+                                break;
+                            case 'r':
+                                buf.append('\r');
+                                break;
+                            case '\\':
+                                buf.append('\\');
+                                break;
+                            case '0':
+                            case '1':
+                            case '2':
+                            case '3':
+                            case '4':
+                            case '5':
+                            case '6':
+                            case '7':
+                            case '8':
+                            case '9':
+                                ch = 0;
+                                for (int j = 0; j < 3; j++) {
+                                    c = i < arg.length() - 1 ? arg.charAt(++i) : -1;
+                                    if (c >= 0) {
+                                        ch = ch * 8 + (c - '0');
+                                    }
+                                }
+                                buf.append((char) ch);
+                                break;
+                            case 'u':
+                                ch = 0;
+                                for (int j = 0; j < 4; j++) {
+                                    c = i < arg.length() - 1 ? arg.charAt(++i) : -1;
+                                    if (c >= 0) {
+                                        if (c >= 'A' && c <= 'Z') {
+                                            ch = ch * 16 + (c - 'A' + 10);
+                                        } else if (c >= 'a' && c <= 'z') {
+                                            ch = ch * 16 + (c - 'a' + 10);
+                                        } else if (c >= '0' && c <= '9') {
+                                            ch = ch * 16 + (c - '0');
+                                        } else {
+                                            break;
+                                        }
+                                    }
+                                }
+                                buf.append((char) ch);
+                                break;
+                            default:
+                                buf.append((char) c);
+                                break;
+                        }
+                    } else {
+                        buf.append((char) c);
+                    }
+                }
             }
         }
         if (opt.isSet("n")) {
diff --git a/gogo/jline/src/main/resources/gosh_profile b/gogo/jline/src/main/resources/gosh_profile
index 1a5822c..068f5d1 100644
--- a/gogo/jline/src/main/resources/gosh_profile
+++ b/gogo/jline/src/main/resources/gosh_profile
@@ -24,7 +24,7 @@
 try {
 
   # ensure gogo commands are found first
-  SCOPE = gogo:*
+  SCOPE = 'gogo:*'
 
   # add methods on BundleContext object as commands
   addcommand context ${.context}
@@ -45,7 +45,9 @@
 
   # set prompt
   prompt = 'g! '
-  \#rprompt = { (new java.text.SimpleDateFormat "HH:mm:ss") format (new Date) }
+  \#rprompt = { (new java.text.SimpleDateFormat \'\u001B\[90m\'HH:mm:ss) format (new Date) }
+  # could also be written
+  #  rprompt = { date +\u001B\\\[90m\%T }
 
 
   __option_not_present = {
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/ArgList.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/ArgList.java
index f61717c..1795ef7 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/ArgList.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/ArgList.java
@@ -29,9 +29,9 @@
  */
 public class ArgList extends AbstractList<Object>
 {
-    private List<Object> list;
+    private List<?> list;
 
-    public ArgList(List<Object> args)
+    public ArgList(List<?> args)
     {
         this.list = args;
     }
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 b4f0e5a..0025f11 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
@@ -23,6 +23,7 @@
 import java.io.PipedOutputStream;
 import java.nio.channels.Channel;
 import java.nio.channels.Channels;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
@@ -665,6 +666,11 @@
     }
 
     @Override
+    public Path currentDir() {
+        return session().currentDir();
+    }
+
+    @Override
     public String toString()
     {
         return source.toString().trim().replaceAll("\n+", "\n").replaceAll(
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 a015a8b..e73ad19 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
@@ -18,6 +18,8 @@
  */
 package org.apache.felix.gogo.runtime;
 
+import java.nio.file.Path;
+
 public interface Evaluate
 {
     Object eval(Token t) throws Exception;
@@ -27,4 +29,6 @@
     Object put(String key, Object value);
 
     Object expr(Token t);
+
+    Path currentDir();
 }
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
index b248562..ab48505 100644
--- 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
@@ -18,9 +18,19 @@
  */
 package org.apache.felix.gogo.runtime;
 
+import java.io.IOException;
+import java.nio.file.FileVisitOption;
+import java.nio.file.FileVisitResult;
+import java.nio.file.FileVisitor;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.nio.file.attribute.BasicFileAttributes;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Function;
@@ -62,6 +72,241 @@
 
     private Object expand() throws Exception
     {
+        Object expanded = doExpand();
+        if (expanded instanceof List) {
+            List<Object> list = new ArrayList<>();
+            for (Object o : ((List) expanded)) {
+                if (o instanceof CharSequence) {
+                    list.addAll(generateFileNames((CharSequence) o));
+                } else {
+                    list.add(o);
+                }
+            }
+            List<Object> unquoted = new ArrayList<>();
+            for (Object o : list) {
+                if (o instanceof CharSequence) {
+                    unquoted.add(unquote((CharSequence) o));
+                } else {
+                    unquoted.add(o);
+                }
+            }
+            if (unquoted.size() == 1) {
+                return unquoted.get(0);
+            }
+            if (expanded instanceof ArgList) {
+                return new ArgList(unquoted);
+            } else {
+                return unquoted;
+            }
+        } else if (expanded instanceof CharSequence) {
+            List<? extends CharSequence> list = generateFileNames((CharSequence) expanded);
+            List<CharSequence> unquoted = new ArrayList<>();
+            for (CharSequence o : list) {
+                unquoted.add(unquote(o));
+            }
+            if (unquoted.size() == 1) {
+                return unquoted.get(0);
+            }
+            return new ArgList(unquoted);
+        }
+        return expanded;
+    }
+
+    private CharSequence unquote(CharSequence arg) {
+        if (inQuote) {
+            return arg;
+        }
+        boolean hasEscape = false;
+        for (int i = 0; i < arg.length(); i++) {
+            int c = arg.charAt(i);
+            if (c == '\\' || c == '"' || c == '\'') {
+                hasEscape = true;
+                break;
+            }
+        }
+        if (!hasEscape) {
+            return arg;
+        }
+        boolean singleQuoted = false;
+        boolean doubleQuoted = false;
+        boolean escaped = false;
+        StringBuilder buf = new StringBuilder(arg.length());
+        for (int i = 0; i < arg.length(); i++) {
+            char c = arg.charAt(i);
+            if (doubleQuoted && escaped) {
+                if (c != '"' && c != '\\' && c != '$' && c != '%') {
+                    buf.append('\\');
+                }
+                buf.append(c);
+                escaped = false;
+            }
+            else if (escaped) {
+                buf.append(c);
+                escaped = false;
+            }
+            else if (singleQuoted) {
+                if (c == '\'') {
+                    singleQuoted = false;
+                } else {
+                    buf.append(c);
+                }
+            }
+            else if (doubleQuoted) {
+                if (c == '\\') {
+                    escaped = true;
+                }
+                else if (c == '\"') {
+                    doubleQuoted = false;
+                }
+                else {
+                    buf.append(c);
+                }
+            }
+            else if (c == '\\') {
+                escaped = true;
+            }
+            else if (c == '\'') {
+                singleQuoted = true;
+            }
+            else if (c == '"') {
+                doubleQuoted = true;
+            }
+            else {
+                buf.append(c);
+            }
+        }
+        return buf.toString();
+    }
+
+    protected List<? extends CharSequence> generateFileNames(CharSequence arg) throws IOException {
+        // Disable if currentDir is not set
+        Path currentDir = evaluate.currentDir();
+        if (currentDir == null) {
+            return Collections.singletonList(arg);
+        }
+        // Search for unquoted escapes
+        boolean hasUnescapedReserved = false;
+        boolean escaped = false;
+        boolean doubleQuoted = false;
+        boolean singleQuoted = false;
+        StringBuilder buf = new StringBuilder(arg.length());
+        String pfx = "";
+        for (int i = 0; i < arg.length(); i++) {
+            char c = arg.charAt(i);
+            if (doubleQuoted && escaped) {
+                if (c != '"' && c != '\\' && c != '$' && c != '%') {
+                    buf.append('\\');
+                }
+                buf.append(c);
+                escaped = false;
+            }
+            else if (escaped) {
+                buf.append(c);
+                escaped = false;
+            }
+            else if (singleQuoted) {
+                if (c == '\'') {
+                    singleQuoted = false;
+                } else {
+                    buf.append(c);
+                }
+            }
+            else if (doubleQuoted) {
+                if (c == '\\') {
+                    escaped = true;
+                }
+                else if (c == '\"') {
+                    doubleQuoted = false;
+                }
+                else {
+                    buf.append(c);
+                }
+            }
+            else if (c == '\\') {
+                escaped = true;
+            }
+            else if (c == '\'') {
+                singleQuoted = true;
+            }
+            else if (c == '"') {
+                doubleQuoted = true;
+            }
+            else {
+                if ("*(|<[?".indexOf(c) >= 0 && !hasUnescapedReserved) {
+                    hasUnescapedReserved = true;
+                    pfx = buf.toString();
+                }
+                buf.append(c);
+            }
+        }
+        if (!hasUnescapedReserved) {
+            return Collections.singletonList(arg);
+        }
+
+        String org = buf.toString();
+        List<String> expanded = new ArrayList<>();
+        Path dir;
+        String prefix;
+        if (pfx.indexOf('/') >= 0) {
+            pfx = pfx.substring(0, pfx.lastIndexOf('/'));
+            arg = org.substring(pfx.length() + 1);
+            dir = currentDir.resolve(pfx).normalize();
+            prefix = pfx + "/";
+        } else {
+            dir = currentDir;
+            prefix = "";
+        }
+        PathMatcher matcher = dir.getFileSystem().getPathMatcher("glob:" + arg);
+        Files.walkFileTree(dir,
+                EnumSet.of(FileVisitOption.FOLLOW_LINKS),
+                Integer.MAX_VALUE,
+                new FileVisitor<Path>() {
+                    @Override
+                    public FileVisitResult preVisitDirectory(Path file, BasicFileAttributes attrs) throws IOException {
+                        if (file.equals(dir)) {
+                            return FileVisitResult.CONTINUE;
+                        }
+                        if (Files.isHidden(file)) {
+                            return FileVisitResult.SKIP_SUBTREE;
+                        }
+                        Path r = dir.relativize(file);
+                        if (matcher.matches(r)) {
+                            expanded.add(prefix + r.toString());
+                        }
+                        return FileVisitResult.CONTINUE;
+                    }
+
+                    @Override
+                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                        if (!Files.isHidden(file)) {
+                            Path r = dir.relativize(file);
+                            if (matcher.matches(r)) {
+                                expanded.add(prefix + r.toString());
+                            }
+                        }
+                        return FileVisitResult.CONTINUE;
+                    }
+
+                    @Override
+                    public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
+                        return FileVisitResult.CONTINUE;
+                    }
+
+                    @Override
+                    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+                        return FileVisitResult.CONTINUE;
+                    }
+                });
+        Collections.sort(expanded);
+        if (expanded.isEmpty()) {
+            throw new IOException("no matches found: " + org);
+        }
+        return expanded;
+    }
+
+
+    private Object doExpand() throws Exception
+    {
         final String special = "%$\\\"'";
         int i = text.length();
         while ((--i >= 0) && (special.indexOf(text.charAt(i)) == -1));
@@ -94,29 +339,34 @@
                     continue; // expandVar() has already read next char
 
                 case '$':
-                    Object val = expandVar();
-
-                    if (EOT == ch && buf.length() == 0)
-                    {
-                        return val;
+                    // Posix quote
+                    if (peek() == '\'') {
+                        getch();
+                        skipQuote();
+                        value = text.subSequence(start + 1, index - 1);
+                        getch();
+                        buf.append("\'");
+                        buf.append(ansiEscape(value));
+                        buf.append("\'");
                     }
-
-                    if (null != val)
-                    {
-                        buf.append(val);
+                    // Parameter expansion
+                    else {
+                        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);
+                    if (peek() != EOT) {
+                        getch();
                         buf.append(ch);
                     }
-
                     break;
 
                 case '"':
@@ -127,17 +377,20 @@
                     if (eot() && buf.length() == 0)
                     {
                         if (expand instanceof ArgList) {
-                            return new ArgList((ArgList) expand).stream().map(String::valueOf).collect(Collectors.toList());
+                            return new ArgList((ArgList) expand).stream()
+                                    .map(String::valueOf)
+                                    .map(s -> "\"" + s + "\"").collect(Collectors.toList());
                         } else if (expand instanceof Collection) {
-                            return ((Collection) expand).stream().map(String::valueOf).collect(Collectors.joining(" "));
+                            return ((Collection) expand).stream().map(String::valueOf).collect(Collectors.joining(" ", "\"", "\""));
                         } else if (expand != null) {
-                            return expand.toString();
+                            return "\"" + expand.toString() + "\"";
                         } else {
                             return "";
                         }
                     }
                     if (expand instanceof Collection) {
                         boolean first = true;
+                        buf.append("\"");
                         for (Object o : ((Collection) expand)) {
                             if (!first) {
                                 buf.append(" ");
@@ -145,9 +398,12 @@
                             first = false;
                             buf.append(o);
                         }
+                        buf.append("\"");
                     }
                     else if (expand != null) {
+                        buf.append("\"");
                         buf.append(expand.toString());
+                        buf.append("\"");
                     }
                     continue; // has already read next char
 
@@ -155,11 +411,11 @@
                     if (!inQuote)
                     {
                         skipQuote();
-                        value = text.subSequence(start, index - 1);
+                        value = text.subSequence(start - 1, index);
 
                         if (eot() && buf.length() == 0)
                         {
-                            return value.toString();
+                            return value;
                         }
 
                         buf.append(value);
@@ -176,6 +432,78 @@
         return buf.toString();
     }
 
+    private CharSequence ansiEscape(CharSequence arg) {
+        StringBuilder buf = new StringBuilder(arg.length());
+        for (int i = 0; i < arg.length(); i++) {
+            int c = arg.charAt(i);
+            int ch;
+            if (c == '\\') {
+                c = i < arg.length() - 1 ? arg.charAt(++i) : '\\';
+                switch (c) {
+                    case 'a':
+                        buf.append('\u0007');
+                        break;
+                    case 'n':
+                        buf.append('\n');
+                        break;
+                    case 't':
+                        buf.append('\t');
+                        break;
+                    case 'r':
+                        buf.append('\r');
+                        break;
+                    case '\\':
+                        buf.append('\\');
+                        break;
+                    case '0':
+                    case '1':
+                    case '2':
+                    case '3':
+                    case '4':
+                    case '5':
+                    case '6':
+                    case '7':
+                    case '8':
+                    case '9':
+                        ch = 0;
+                        for (int j = 0; j < 3; j++) {
+                            c = i < arg.length() - 1 ? arg.charAt(++i) : -1;
+                            if (c >= 0) {
+                                ch = ch * 8 + (c - '0');
+                            }
+                        }
+                        buf.append((char) ch);
+                        break;
+                    case 'u':
+                        ch = 0;
+                        for (int j = 0; j < 4; j++) {
+                            c = i < arg.length() - 1 ? arg.charAt(++i) : -1;
+                            if (c >= 0) {
+                                if (c >= 'A' && c <= 'F') {
+                                    ch = ch * 16 + (c - 'A' + 10);
+                                } else if (c >= 'a' && c <= 'f') {
+                                    ch = ch * 16 + (c - 'a' + 10);
+                                } else if (c >= '0' && c <= '9') {
+                                    ch = ch * 16 + (c - '0');
+                                } else {
+                                    i--;
+                                    break;
+                                }
+                            }
+                        }
+                        buf.append((char) ch);
+                        break;
+                    default:
+                        buf.append((char) c);
+                        break;
+                }
+            } else {
+                buf.append((char) c);
+            }
+        }
+        return buf;
+    }
+
     private Object expandExp()
     {
         assert '%' == ch;
@@ -524,7 +852,7 @@
                             val = ((List) val).get(Integer.parseInt(left.toString()));
                         }
                     }
-                    else {
+                    else if (val != null) {
                         if (left.toString().equals("@")) {
                             val = val.toString();
                         } else {
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 2dffb5a..239fba4 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
@@ -114,6 +114,9 @@
         c.addCommand("echoout", this);
         c.execute("myecho = { echoout $args }");
 
+        // Disable file name generation to avoid escaping 'd.*'
+        c.currentDir(null);
+
         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"));
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 1966d43..5cc4119 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
@@ -31,8 +31,10 @@
         Context c = new Context();
         c.addCommand("echo", this);
 
+        c.currentDir(null);
+
         assertEquals("file://wibble#tag", c.execute("echo file://wibble#tag"));
-        assertEquals("file:", c.execute("echo file: //wibble#tag"));
+//CHANGE        assertEquals("file:", c.execute("echo file: //wibble#tag"));
 
         assertEquals("PWD/*.java", c.execute("echo PWD/*.java"));
         try
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 9a7cc03..aede37a 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
@@ -22,6 +22,7 @@
 import java.io.File;
 import java.io.PrintStream;
 import java.net.URI;
+import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
@@ -66,6 +67,10 @@
             public Object expr(Token t) {
                 throw new UnsupportedOperationException("expr not implemented.");
             }
+
+            public Path currentDir() {
+                return null;
+            }
         };
     }
 
@@ -215,7 +220,7 @@
         assertEquals("hello\\w", expand("\"hello\\\\w\""));
         assertEquals("hello\\w", expand("\"hello\\w\""));
         assertEquals("hello\\\\w", expand("'hello\\\\w'"));
-        assertEquals("hello", expand("he\\\nllo"));
+//CHANGE        assertEquals("hello", expand("he\\\nllo"));
         assertEquals("he\\llo", expand("'he\\llo'"));
         assertEquals("he'llo", expand("'he'\\''llo'"));
         assertEquals("he\"llo", expand("\"he\\\"llo\""));
@@ -229,26 +234,11 @@
         // Note: we could use literal Unicode pound '£' instead of \u00a3 in next test.
         // if above is not UK currency symbol, then your locale is not configured for UTF-8.
         // Java on Macs cannot handle UTF-8 unless you explicitly set '-Dfile.encoding=UTF-8'.
-        assertEquals("pound\u00a3cent\u00a2", expand("pound\\u00a3cent\\u00a2"));
-        assertEquals("euro\\u20ac", expand("'euro\\u20ac'"));
-        try
-        {
-            expand("eot\\u20a");
-            fail("EOT in unicode");
-        }
-        catch (SyntaxError e)
-        {
-            // expected
-        }
-        try
-        {
-            expand("bad\\u20ag");
-            fail("bad unicode");
-        }
-        catch (SyntaxError e)
-        {
-            // expected
-        }
+        assertEquals("pound\u00a3cent\u00a2", expand("$'pound\\u00a3cent\\u00a2'"));
+        assertEquals("euro\\u20ac", expand("$'euro\\\\u20ac'"));
+        assertEquals("euro\u20ac", expand("$'euro\\u20ac'"));
+        assertEquals("euro\u020a", expand("$'euro\\u20a'"));
+        assertEquals("euro\u020ag", expand("$'euro\\u20ag'"));
 
         // simple variable expansion - quoting or concatenation converts result to String
         assertEquals(user, expand("$USER"));
@@ -304,7 +294,7 @@
         {
             // expected
         }
-        assertEquals(user, expand("${US\\u0045R:?}"));
+//CHANGE        assertEquals(user, expand("${US\\u0045R:?}"));
 
         // bash doesn't supported nested expansions
         // gogo only supports them in the ${} syntax