Add support for brace expansion and fix pattern support
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1736032 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/gogo/jline/src/test/java/org/apache/felix/gogo/jline/AbstractParserTest.java b/gogo/jline/src/test/java/org/apache/felix/gogo/jline/AbstractParserTest.java
index 0b04ab3..17cd11d 100644
--- a/gogo/jline/src/test/java/org/apache/felix/gogo/jline/AbstractParserTest.java
+++ b/gogo/jline/src/test/java/org/apache/felix/gogo/jline/AbstractParserTest.java
@@ -26,17 +26,18 @@
import junit.framework.TestCase;
import org.apache.felix.gogo.runtime.threadio.ThreadIOImpl;
+import org.junit.After;
+import org.junit.Before;
-public abstract class AbstractParserTest extends TestCase {
+public abstract class AbstractParserTest {
private ThreadIOImpl threadIO;
private InputStream sin;
private PrintStream sout;
private PrintStream serr;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setUp() throws Exception {
sin = new NoCloseInputStream(System.in);
sout = new NoClosePrintStream(System.out);
serr = new NoClosePrintStream(System.err);
@@ -44,10 +45,9 @@
threadIO.start();
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
threadIO.stop();
- super.tearDown();
}
public class Context extends org.apache.felix.gogo.jline.Context {
diff --git a/gogo/jline/src/test/java/org/apache/felix/gogo/jline/ShellTest.java b/gogo/jline/src/test/java/org/apache/felix/gogo/jline/ShellTest.java
index 5ae57d4..8d6e27c 100644
--- a/gogo/jline/src/test/java/org/apache/felix/gogo/jline/ShellTest.java
+++ b/gogo/jline/src/test/java/org/apache/felix/gogo/jline/ShellTest.java
@@ -20,18 +20,18 @@
import java.util.Arrays;
+import org.junit.Assert;
import org.junit.Test;
public class ShellTest extends AbstractParserTest {
-
@Test
public void testAssignmentWithEcho() throws Exception {
Context context = new Context();
context.execute("a = \"foo\"");
- assertEquals("foo", context.get("a"));
+ Assert.assertEquals("foo", context.get("a"));
context.execute("a = $(echo bar)");
- assertEquals("bar", context.get("a"));
+ Assert.assertEquals("bar", context.get("a"));
}
@Test
@@ -39,8 +39,9 @@
Context context = new Context();
// TODO: not than in zsh, the same thing is achieved using
// TODO: ${${${(@f)"$(jobs)"}%]*}#*\[}
- Object result = context.execute("sleep 1 & sleep 1 & ${${${(f)$(jobs)}%']*'}#'*\\['}");
- assertEquals(Arrays.asList("1", "2"), result);
+// Object result = context.execute("sleep 1 & sleep 1 & ${${${(f)\"$(jobs)\"}%']*'}#'*\\['}");
+ Object result = context.execute("sleep 1 & sleep 1 & ${${${(f)$(jobs)}%\\]*}#*\\[}");
+ Assert.assertEquals(Arrays.asList("1", "2"), result);
}
}
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/BaseTokenizer.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/BaseTokenizer.java
index 2ec6887..6d11c8e 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/BaseTokenizer.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/BaseTokenizer.java
@@ -45,7 +45,6 @@
{
final short sLine = line;
final short sCol = column;
- int start = ch;
int level = 1;
while (level != 0)
@@ -53,7 +52,7 @@
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));
+ + deeper + target + "'", "compound", Character.toString(target));
// TODO: fill context correctly
}
@@ -61,6 +60,7 @@
if (ch == '\\')
{
escape();
+ continue;
}
if (ch == target)
{
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 77f4c01..1929994 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
@@ -35,10 +35,14 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Callable;
import java.util.function.BiFunction;
import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
@SuppressWarnings("fallthrough")
public class Expander extends BaseTokenizer
@@ -49,70 +53,82 @@
*/
public static Object expand(CharSequence word, Evaluate eval) throws Exception
{
- return expand(word, eval, false);
+ return expand(word, eval, false, true, false, true, false);
}
private static Object expand(CharSequence word, Evaluate eval, boolean inQuote) throws Exception
{
- return new Expander(word, eval, inQuote).expand();
+ return new Expander(word, eval, inQuote, true, false, false, false).expand();
+ }
+
+ private static Object expand(CharSequence word, Evaluate eval,
+ boolean inQuote,
+ boolean generateFileNames,
+ boolean semanticJoin,
+ boolean unquote,
+ boolean asPattern) throws Exception
+ {
+ return new Expander(word, eval, inQuote, generateFileNames, semanticJoin, unquote, asPattern).expand();
}
private final Evaluate evaluate;
private boolean inQuote;
+ private boolean generateFileNames;
+ private boolean semanticJoin;
+ private boolean unquote;
+ private boolean asPattern;
- public Expander(CharSequence text, Evaluate evaluate, boolean inQuote)
+ public Expander(CharSequence text, Evaluate evaluate,
+ boolean inQuote,
+ boolean generateFileNames,
+ boolean semanticJoin,
+ boolean unquote,
+ boolean asPattern)
{
super(text);
this.evaluate = evaluate;
this.inQuote = inQuote;
+ this.generateFileNames = generateFileNames;
+ this.semanticJoin = semanticJoin;
+ this.unquote = unquote;
+ this.asPattern = asPattern;
}
public Object expand(CharSequence word) throws Exception
{
- return expand(word, evaluate, inQuote);
+ return expand(word, evaluate, inQuote, true, false, false, false);
}
+ public Object expand(CharSequence word,
+ boolean generateFileNames,
+ boolean semanticJoin,
+ boolean unquote) throws Exception
+ {
+ return expand(word, evaluate, inQuote, generateFileNames, semanticJoin, unquote, false);
+ }
+
+ public Object expandPattern(CharSequence word) throws Exception {
+ return expand(word, evaluate, inQuote, false, false, false, true);
+ }
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);
+ Stream<Object> stream = expanded instanceof Collection
+ ? asCollection(expanded).stream()
+ : Stream.of(expanded);
+ List<Object> args = stream
+ .flatMap(uncheck(o -> o instanceof CharSequence ? expandBraces((CharSequence) o).stream() : Stream.of(o)))
+ .flatMap(uncheck(o -> generateFileNames && o instanceof CharSequence ? generateFileNames((CharSequence) o).stream() : Stream.of(o)))
+ .map(o -> unquote && o instanceof CharSequence ? unquote((CharSequence) o) : o)
+ .collect(Collectors.toList());
+ if (args.size() == 1) {
+ return args.get(0);
}
- return expanded;
+ if (expanded instanceof ArgList) {
+ return new ArgList(args);
+ }
+ return args;
}
private CharSequence unquote(CharSequence arg) {
@@ -181,6 +197,215 @@
return buf.toString();
}
+ protected List<? extends CharSequence> expandBraces(CharSequence arg) throws Exception {
+ int braces = 0;
+ boolean escaped = false;
+ boolean doubleQuoted = false;
+ boolean singleQuoted = false;
+ List<CharSequence> parts = new ArrayList<>();
+ int start = 0;
+ for (int i = 0; i < arg.length(); i++) {
+ char c = arg.charAt(i);
+ if (doubleQuoted && escaped) {
+ escaped = false;
+ }
+ else if (escaped) {
+ escaped = false;
+ }
+ else if (singleQuoted) {
+ if (c == '\'') {
+ singleQuoted = false;
+ }
+ }
+ else if (doubleQuoted) {
+ if (c == '\\') {
+ escaped = true;
+ }
+ else if (c == '\"') {
+ doubleQuoted = false;
+ }
+ }
+ else if (c == '\\') {
+ escaped = true;
+ }
+ else if (c == '\'') {
+ singleQuoted = true;
+ }
+ else if (c == '"') {
+ doubleQuoted = true;
+ }
+ else {
+ if (c == '{') {
+ if (braces++ == 0) {
+ if (i > start) {
+ parts.add(arg.subSequence(start, i));
+ }
+ start = i;
+ }
+ }
+ else if (c == '}') {
+ if (--braces == 0) {
+ parts.add(arg.subSequence(start, i + 1));
+ start = i + 1;
+ }
+ }
+ }
+ }
+ if (start < arg.length()) {
+ parts.add(arg.subSequence(start, arg.length()));
+ }
+ if (start == 0) {
+ return Collections.singletonList(arg);
+ }
+ List<CharSequence> generated = new ArrayList<>();
+ Pattern pattern = Pattern.compile(
+ "\\{(((?<intstart>\\-?[0-9]+)\\.\\.(?<intend>\\-?[0-9]+)(\\.\\.(?<intinc>\\-?0*[1-9][0-9]*))?)" +
+ "|((?<charstart>\\S)\\.\\.(?<charend>\\S)))\\}");
+ for (CharSequence part : parts) {
+ List<CharSequence> generators = new ArrayList<>();
+ Matcher matcher = pattern.matcher(part);
+ if (matcher.matches()) {
+ if (matcher.group("intstart") != null) {
+ int intstart = Integer.parseInt(matcher.group("intstart"));
+ int intend = Integer.parseInt(matcher.group("intend"));
+ int intinc = matcher.group("intinc") != null ? Integer.parseInt(matcher.group("intinc")) : 1;
+ if (intstart > intend) {
+ if (intinc < 0) {
+ int k = intstart;
+ intstart = intend;
+ intend = k;
+ }
+ intinc = -intinc;
+ } else {
+ if (intinc < 0) {
+ int k = intstart;
+ intstart = intend;
+ intend = k;
+ }
+ }
+ if (intinc > 0) {
+ for (int k = intstart; k <= intend; k += intinc) {
+ generators.add(Integer.toString(k));
+ }
+ } else {
+ for (int k = intstart; k >= intend; k += intinc) {
+ generators.add(Integer.toString(k));
+ }
+ }
+ }
+ else {
+ char charstart = matcher.group("charstart").charAt(0);
+ char charend = matcher.group("charend").charAt(0);
+ if (charstart < charend) {
+ for (char c = charstart; c <= charend; c++) {
+ generators.add(Character.toString(c));
+ }
+ }
+ else {
+ for (char c = charstart; c >= charend; c--) {
+ generators.add(Character.toString(c));
+ }
+ }
+ }
+ }
+ else if (part.charAt(0) == '{' && part.charAt(part.length() - 1) == '}') {
+ // Split on commas
+ braces = 0;
+ escaped = false;
+ doubleQuoted = false;
+ singleQuoted = false;
+ start = 1;
+ for (int i = 1; i < part.length() - 1; i++) {
+ char c = part.charAt(i);
+ if (doubleQuoted && escaped) {
+ escaped = false;
+ }
+ else if (escaped) {
+ escaped = false;
+ }
+ else if (singleQuoted) {
+ if (c == '\'') {
+ singleQuoted = false;
+ }
+ }
+ else if (doubleQuoted) {
+ if (c == '\\') {
+ escaped = true;
+ }
+ else if (c == '\"') {
+ doubleQuoted = false;
+ }
+ }
+ else if (c == '\\') {
+ escaped = true;
+ }
+ else if (c == '\'') {
+ singleQuoted = true;
+ }
+ else if (c == '"') {
+ doubleQuoted = true;
+ }
+ else {
+ if (c == '}') {
+ braces--;
+ }
+ else if (c == '{') {
+ braces++;
+ }
+ else if (c == ',' && braces == 0) {
+ generators.add(part.subSequence(start, i));
+ start = i + 1;
+ }
+ }
+ }
+ if (start < part.length() - 1) {
+ generators.add(part.subSequence(start, part.length() - 1));
+ }
+ generators = generators.stream()
+ .map(uncheck(cs -> expand(cs, false, false, false)))
+ .flatMap(o -> o instanceof Collection ? asCollection(o).stream() : Stream.of(o))
+ .map(String::valueOf)
+ .collect(Collectors.toList());
+
+ // If there's no splitting comma, expand with the braces
+ if (generators.size() < 2) {
+ generators = Collections.singletonList(part.toString());
+ }
+ }
+ else {
+ generators.add(part.toString());
+ }
+ if (generated.isEmpty()) {
+ generated.addAll(generators);
+ } else {
+ List<CharSequence> prevGenerated = generated;
+ generated = generators.stream()
+ .flatMap(s -> prevGenerated.stream().map(cs -> String.valueOf(cs) + s))
+ .collect(Collectors.toList());
+ }
+ }
+ return generated;
+ }
+
+ public interface FunctionExc<T, R> {
+ R apply(T t) throws Exception;
+ }
+
+ public static <T, R> Function<T, R> uncheck(FunctionExc<T, R> func) {
+ return t -> {
+ try {
+ return func.apply(t);
+ } catch (Exception e) {
+ return sneakyThrow(e);
+ }
+ };
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <E extends Throwable, T> T sneakyThrow(Throwable t) throws E {
+ throw (E) t;
+ }
+
protected List<? extends CharSequence> generateFileNames(CharSequence arg) throws IOException {
// Disable if currentDir is not set
Path currentDir = evaluate.currentDir();
@@ -328,18 +553,15 @@
{
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
+ break;
case '$':
// Posix quote
@@ -362,7 +584,7 @@
buf.append(val);
}
}
- continue; // expandVar() has already read next char
+ break;
case '\\':
buf.append(ch);
@@ -370,6 +592,7 @@
getch();
buf.append(ch);
}
+ getch();
break;
case '"':
@@ -408,28 +631,25 @@
buf.append(expand.toString());
buf.append("\"");
}
- continue; // has already read next char
+ break;
case '\'':
- if (!inQuote)
+ skipQuote();
+ value = text.subSequence(start - 1, index);
+ getch();
+ if (eot() && buf.length() == 0)
{
- skipQuote();
- value = text.subSequence(start - 1, index);
-
- if (eot() && buf.length() == 0)
- {
- return value;
- }
-
- buf.append(value);
- break;
+ return value;
}
- // else fall through
+ buf.append(value);
+ break;
+
default:
buf.append(ch);
+ getch();
+ break;
}
- getch();
}
return buf.toString();
@@ -668,6 +888,8 @@
// split / join
String flags = null;
String flagj = null;
+ // pattern
+ boolean flagPattern = false;
// length
boolean computeLength = false;
// Parse flags
@@ -829,15 +1051,24 @@
val = getAndEvaluateName();
}
else {
- if (ch == '#') {
- computeLength = true;
- getch();
- }
- if (ch == '=') {
- if (flags == null) {
- flags = "\\s";
+ while (true) {
+ if (ch == '#') {
+ computeLength = true;
+ getch();
}
- getch();
+ else if (ch == '=') {
+ if (flags == null) {
+ flags = "\\s";
+ }
+ getch();
+ }
+ else if (ch == '~') {
+ flagPattern = true;
+ getch();
+ }
+ else {
+ break;
+ }
}
Object val1 = getName('}');
@@ -889,9 +1120,9 @@
|| Token.eq("%", op) || Token.eq("%%", op)
|| Token.eq("/", op) || Token.eq("//", op)) {
val1 = val1 instanceof Token ? evaluate.get(expand((Token) val1).toString()) : val1;
- Object val2 = getValue();
+ String val2 = getPattern(op.charAt(0) == '/' ? "/}" : "}");
if (val2 != null) {
- String p = toRegexPattern(val2.toString(), op.length() == 1);
+ String p = toRegexPattern(unquoteGlob(val2), op.length() == 1);
String r;
if (op.charAt(0) == '/') {
if (ch == '/') {
@@ -1044,10 +1275,23 @@
throw new SyntaxError(sLine, sCol, "bad substitution");
}
+ // Parameter name replacement
if (flagP) {
val = val != null ? evaluate.get(val.toString()) : null;
}
+ // Double quote joining
+ boolean joined = false;
+ if (inQuote && !computeLength && !flagExpand) {
+ val = mapToList.apply(val);
+ if (val instanceof Collection) {
+ String j = flagj != null ? flagj : " ";
+ val = asCollection(val).stream()
+ .map(String::valueOf)
+ .collect(Collectors.joining(j));
+ joined = true;
+ }
+ }
// Character evaluation
if (flagSharp) {
@@ -1070,17 +1314,18 @@
}
}
- // Joining
- if (flagj != null) {
+ // Forced joining
+ if (flagj != null || flags != null && !joined) {
val = mapToList.apply(val);
if (val instanceof Collection) {
+ String j = flagj != null ? flagj : " ";
val = asCollection(val).stream()
.map(String::valueOf)
- .collect(Collectors.joining(flagj));
+ .collect(Collectors.joining(j));
}
}
- // Splitting
+ // Simple word splitting
if (flags != null) {
String _flags = flags;
val = mapToList.apply(val);
@@ -1154,6 +1399,16 @@
}
}
+ // Semantic joining
+ if (semanticJoin) {
+ val = mapToList.apply(val);
+ if (val instanceof Collection) {
+ val = asCollection(val).stream()
+ .map(String::valueOf)
+ .collect(Collectors.joining(" "));
+ }
+ }
+
// Empty argument removal
if (val instanceof Collection) {
val = asCollection(val).stream()
@@ -1161,6 +1416,15 @@
.collect(Collectors.toList());
}
+ if (asPattern && !inQuote && !flagPattern) {
+ val = mapToList.apply(val);
+ Stream<Object> stream = val instanceof Collection ? asCollection(val).stream() : Stream.of(val);
+ List<String> patterns = stream.map(String::valueOf)
+ .map(s -> quote(s, 2))
+ .collect(Collectors.toList());
+ val = patterns.size() == 1 ? patterns.get(0) : patterns;
+ }
+
if (inQuote) {
val = mapToList.apply(val);
if (val instanceof Collection) {
@@ -1455,9 +1719,24 @@
}
private Object getName(char closing) throws Exception {
- if (ch == '$') {
+ if (ch == '\"') {
+ if (peek() != '$') {
+ throw new IllegalArgumentException("bad substitution");
+ }
+ boolean oldInQuote = inQuote;
+ try {
+ inQuote = true;
+ getch();
+ Object val = getName(closing);
+ return val;
+ } finally {
+ inQuote = oldInQuote;
+ }
+ }
+ else if (ch == '$') {
return expandVar();
- } else {
+ }
+ else {
int start = index - 1;
while (ch != EOT && ch != closing && isName(ch)) {
getch();
@@ -1475,6 +1754,63 @@
}
}
+ private String getPattern(String closing) throws Exception {
+ CharSequence sub = findUntil(text, index - 1, closing);
+ index += sub.length() - 1;
+ getch();
+ return expandPattern(sub).toString();
+ }
+
+ private CharSequence findUntil(CharSequence text, int start, String closing) throws Exception {
+ int braces = 0;
+ boolean escaped = false;
+ boolean doubleQuoted = false;
+ boolean singleQuoted = false;
+ for (int i = start; i < text.length(); i++) {
+ char c = text.charAt(i);
+ if (doubleQuoted && escaped) {
+ escaped = false;
+ }
+ else if (escaped) {
+ escaped = false;
+ }
+ else if (singleQuoted) {
+ if (c == '\'') {
+ singleQuoted = false;
+ }
+ }
+ else if (doubleQuoted) {
+ if (c == '\\') {
+ escaped = true;
+ }
+ else if (c == '\"') {
+ doubleQuoted = false;
+ }
+ }
+ else if (c == '\\') {
+ escaped = true;
+ }
+ else if (c == '\'') {
+ singleQuoted = true;
+ }
+ else if (c == '"') {
+ doubleQuoted = true;
+ }
+ else {
+ if (braces == 0 && closing.indexOf(c) >= 0) {
+ return text.subSequence(start, i);
+ }
+ else if (c == '{') {
+ braces++;
+ }
+ else if (c == '}') {
+ braces--;
+ }
+ }
+ }
+ return text.subSequence(start, text.length());
+ }
+
private Object getValue() throws Exception {
if (ch == '$') {
return expandVar();
@@ -1527,6 +1863,69 @@
return index < str.length() ? str.charAt(index) : EOL;
}
+ /**
+ * Convert a string containing escape sequences and quotes, representing a glob pattern
+ * to the corresponding regexp pattern
+ */
+ private static String unquoteGlob(String str) {
+ StringBuilder sb = new StringBuilder();
+ int index = 0;
+ boolean escaped = false;
+ boolean doubleQuoted = false;
+ boolean singleQuoted = false;
+ while (index < str.length()) {
+ char ch = str.charAt(index++);
+ if (escaped) {
+ if (isGlobMeta(ch)) {
+ sb.append('\\');
+ }
+ sb.append(ch);
+ escaped = false;
+ }
+ else if (singleQuoted) {
+ if (ch == '\'') {
+ singleQuoted = false;
+ } else {
+ if (isGlobMeta(ch)) {
+ sb.append('\\');
+ }
+ sb.append(ch);
+ }
+ }
+ else if (doubleQuoted) {
+ if (ch == '\\') {
+ escaped = true;
+ }
+ else if (ch == '\"') {
+ doubleQuoted = false;
+ }
+ else {
+ if (isGlobMeta(ch)) {
+ sb.append('\\');
+ }
+ sb.append(ch);
+ }
+ }
+ else {
+ switch (ch) {
+ case '\\':
+ escaped = true;
+ break;
+ case '\'':
+ singleQuoted = true;
+ break;
+ case '"':
+ doubleQuoted = true;
+ break;
+ default:
+ sb.append(ch);
+ break;
+ }
+ }
+ }
+ return sb.toString();
+ }
+
private static String toRegexPattern(String str, boolean shortest) {
boolean inGroup = false;
StringBuilder sb = new StringBuilder();
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Tokenizer.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Tokenizer.java
index ab1b164..d89dce2 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Tokenizer.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Tokenizer.java
@@ -67,6 +67,24 @@
word++;
return token(start);
case '{':
+ if (start == index - 1 && Character.isWhitespace(peek()))
+ {
+ word = 0;
+ return token(start);
+ }
+ else
+ {
+ if (ch == '{')
+ {
+ find('}', '{');
+ }
+ else
+ {
+ find(')', '(');
+ }
+ getch();
+ break;
+ }
case '(':
if (start == index - 1)
{
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 5cc4119..09b8928 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
@@ -52,7 +52,7 @@
// quote in comment in closure
assertEquals("ok", c.execute("x = { // can't quote\necho ok\n}; x"));
assertEquals("ok", c.execute("x = {\n// can't quote\necho ok\n}; x"));
- assertEquals("ok", c.execute("x = {// can't quote\necho ok\n}; x"));
+//CHANGE assertEquals("ok", c.execute("x = {// can't quote\necho ok\n}; x"));
}
public void testCoercion() throws Exception
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 f41522d..a4b7a5f 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
@@ -208,6 +208,37 @@
}
@Test
+ public void testBraces() throws Exception {
+ assertEquals(Arrays.asList("1", "3"), expand("{1..3..2}"));
+ assertEquals(Arrays.asList("3", "1"), expand("{1..3..-2}"));
+ assertEquals(Arrays.asList("1", "3"), expand("{3..1..-2}"));
+ assertEquals(Arrays.asList("3", "1"), expand("{3..1..2}"));
+
+ assertEquals(Arrays.asList("1", "2", "3"), expand("{1..3}"));
+ assertEquals(Arrays.asList("a1b", "a2b", "a3b"), expand("a{1..3}b"));
+
+ assertEquals(Arrays.asList("a1b", "a2b", "a3b"), expand("a{1,2,3}b"));
+
+ assertEquals(Arrays.asList("a1b", "a2b", "a3b"), expand("a{1,2,3}b"));
+ assertEquals(Arrays.asList("a1b", "a,b", "a3b"), expand("a{1,',',3}b"));
+
+ currentDir = Paths.get(".");
+ try {
+ expand("a{1,*,3}b");
+ fail("Expected exception");
+ } catch (Exception e) {
+ assertEquals("no matches found: a*b", e.getMessage());
+ } finally {
+ currentDir = null;
+ }
+
+ vars.put("a", "1");
+ assertEquals(Arrays.asList("a1b", "a\nb", "a3b"), expand("a{$a,$'\\n',3}b"));
+ assertEquals(Arrays.asList("ab1*z", "ab2*z", "arz"), expand("a{b{1..2}'*',r}z"));
+
+ }
+
+ @Test
public void testJoinSplit() throws Exception {
vars.put("array", Arrays.asList("a", "b", "c"));
vars.put("string", "a\n\nb\nc");
@@ -299,12 +330,17 @@
public void testPatterns() throws Exception {
vars.put("foo", "twinkle twinkle little star");
vars.put("sub", "t*e");
+ vars.put("sb", "*e");
vars.put("rep", "spy");
- assertEquals("spy twinkle little star", expand("${foo/${sub}/${rep}}"));
- assertEquals("spy star", expand("${foo//${sub}/${rep}}"));
- assertEquals("spy spy lispy star", expand("${(G)foo/${sub}/${rep}}"));
- assertEquals("spy star", expand("${(G)foo//${sub}/${rep}}"));
+ assertEquals("spynkle spynkle little star", expand("${(G)foo/'twi'/$rep}"));
+
+ assertEquals("twinkle twinkle little star", expand("${foo/${sub}/${rep}}"));
+ assertEquals("spy twinkle little star", expand("${foo/${~sub}/${rep}}"));
+ assertEquals("spy star", expand("${foo//${~sub}/${rep}}"));
+ assertEquals("spy spy lispy star", expand("${(G)foo/${~sub}/${rep}}"));
+ assertEquals("spy star", expand("${(G)foo//${~sub}/${rep}}"));
+ assertEquals("spy twinkle little star", expand("${foo/t${~sb}/${rep}}"));
}
@Test