Improve procedural, add loop break command

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1736047 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Procedural.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Procedural.java
index d1bf87e..054b2f1 100644
--- a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Procedural.java
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Procedural.java
@@ -18,102 +18,303 @@
  */
 package org.apache.felix.gogo.jline;
 
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 
 import org.apache.felix.service.command.CommandSession;
 import org.apache.felix.service.command.Function;
+import org.jline.builtins.Options;
 
 public class Procedural {
 
-    static final String[] functions = {"each", "if", "not", "throw", "try", "until", "while"};
+    static final String[] functions = {"each", "if", "not", "throw", "try", "until", "while", "break"};
 
-    public List<Object> each(CommandSession session, Collection<Object> list,
-                             Function closure) throws Exception {
+    public void _main(CommandSession session, Object[] argv) throws Throwable {
+        if (argv == null || argv.length < 1) {
+            throw new IllegalArgumentException();
+        }
+        try {
+            run(session, argv);
+        } catch (IllegalArgumentException e) {
+            System.err.println(e.getMessage());
+            session.error(2);
+        } catch (HelpException e) {
+            System.err.println(e.getMessage());
+            session.error(0);
+        } catch (ThrownException e) {
+            session.error(1);
+            throw e.getCause();
+        } catch (Exception e) {
+            System.err.println(argv[0] + ": " + e.getMessage());
+            session.error(1);
+        }
+    }
+
+    protected static class HelpException extends Exception {
+        public HelpException(String message) {
+            super(message);
+        }
+    }
+
+    protected static class ThrownException extends Exception {
+        public ThrownException(Throwable cause) {
+            super(cause);
+        }
+    }
+
+    protected static class BreakException extends Exception {
+    }
+
+    protected Options parseOptions(CommandSession session, String[] usage, Object[] argv) throws HelpException {
+        Options opt = Options.compile(usage, s -> get(session, s)).parse(argv, true);
+        if (opt.isSet("help")) {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            opt.usage(new PrintStream(baos));
+            throw new HelpException(baos.toString());
+        }
+        return opt;
+    }
+
+    protected String get(CommandSession session, String name) {
+        Object o = session.get(name);
+        return o != null ? o.toString() : null;
+    }
+
+    protected Object run(CommandSession session, Object[] argv) throws Throwable {
+        switch (argv[0].toString()) {
+            case "each":
+                return doEach(session, argv);
+            case "if":
+                return doIf(session, argv);
+            case "not":
+                return doNot(session, argv);
+            case "throw":
+                return doThrow(session, argv);
+            case "try":
+                return doTry(session, argv);
+            case "until":
+                return doUntil(session, argv);
+            case "while":
+                return doWhile(session, argv);
+            case "break":
+                return doBreak(session, argv);
+            default:
+                throw new UnsupportedOperationException();
+        }
+    }
+
+    protected List<Object> doEach(CommandSession session,
+                                Object[] argv) throws Exception {
+        String[] usage = {
+                "each -  loop over the elements",
+                "Usage: each [-r] elements { closure }",
+                "         elements              an array to iterate on",
+                "         closure               a closure to call",
+                "  -? --help                    Show help",
+                "  -r --result                  Return a list containing each iteration result",
+        };
+        Options opt = parseOptions(session, usage, argv);
+
+        Collection<Object> elements = getElements(opt);
+        List<Function> functions = getFunctions(opt);
+
+        if (elements == null || functions == null || functions.size() != 1) {
+            System.err.println("usage: each elements { closure }");
+            System.err.println("       elements: an array to iterate on");
+            System.err.println("       closure: a function or closure to call");
+            session.error(2);
+            return null;
+        }
+
         List<Object> args = new ArrayList<>();
         List<Object> results = new ArrayList<>();
         args.add(null);
 
-        for (Object x : list) {
+        for (Object x : elements) {
             checkInterrupt();
             args.set(0, x);
-            results.add(closure.execute(session, args));
-        }
-
-        return results;
-    }
-
-    public Object _if(CommandSession session, Function[] fns) throws Exception {
-        int length = fns.length;
-        if (length < 2) {
-            throw new IllegalArgumentException(
-                    "Usage: if {condition} {if-action} ... {else-action}");
-        }
-
-        for (int i = 0; i < length; ++i) {
-            if (i == length - 1 || isTrue(fns[i++].execute(session, null))) {
-                return fns[i].execute(session, null);
+            try {
+                results.add(functions.get(0).execute(session, args));
+            } catch (BreakException b) {
+                break;
             }
         }
 
+        return opt.isSet("result") ? results : null;
+    }
+
+    protected Object doIf(CommandSession session, Object[] argv) throws Exception {
+        String[] usage = {
+                "if -  if / then / else construct",
+                "Usage: if {condition} {if-action} ... {else-action}",
+                "  -? --help                    Show help",
+        };
+        Options opt = parseOptions(session, usage, argv);
+        List<Function> functions = getFunctions(opt);
+        if (functions == null || functions.size() < 2) {
+            System.err.println("usage: if {condition} {if-action} ... {else-action}");
+            session.error(2);
+            return null;
+        }
+        for (int i = 0, length = functions.size(); i < length; ++i) {
+            if (i == length - 1 || isTrue(session, ((Function) opt.argObjects().get(i++)))) {
+                return ((Function) opt.argObjects().get(i)).execute(session, null);
+            }
+        }
         return null;
     }
 
-    public boolean not(CommandSession session, Function condition) throws Exception {
-        return condition == null || !isTrue(condition.execute(session, null));
-
-    }
-
-    // Reflective.coerce() prefers to construct a new Throwable(String)
-    // than to call this method directly.
-    public void _throw(String message) {
-        throw new IllegalArgumentException(message);
-    }
-
-    public void _throw(Exception e) throws Exception {
-        throw e;
-    }
-
-    public void _throw(CommandSession session) throws Throwable {
-        Object exception = session.get("exception");
-        if (exception instanceof Throwable)
-            throw (Throwable) exception;
-        else
-            throw new IllegalArgumentException("exception not set or not Throwable.");
-    }
-
-    public Object _try(CommandSession session, Function func) throws Exception {
-        try {
-            return func.execute(session, null);
-        } catch (Exception e) {
-            session.put("exception", e);
+    protected Boolean doNot(CommandSession session, Object[] argv) throws Exception {
+        String[] usage = {
+                "not -  return the opposite condition",
+                "Usage: not { condition }",
+                "  -? --help                    Show help",
+        };
+        Options opt = parseOptions(session, usage, argv);
+        List<Function> functions = getFunctions(opt);
+        if (functions == null || functions.size() != 1) {
+            System.err.println("usage: not { condition }");
+            session.error(2);
             return null;
         }
+        return !isTrue(session, functions.get(0));
+
     }
 
-    public Object _try(CommandSession session, Function func, Function error)
-            throws Exception {
+    protected Object doThrow(CommandSession session, Object[] argv) throws ThrownException, HelpException {
+        String[] usage = {
+                "throw -  throw an exception",
+                "Usage: throw [ message [ cause ] ]",
+                "       throw exception",
+                "       throw",
+                "  -? --help                    Show help",
+        };
+        Options opt = parseOptions(session, usage, argv);
+        if (opt.argObjects().size() == 0) {
+            Object exception = session.get("exception");
+            if (exception instanceof Throwable)
+                throw new ThrownException((Throwable) exception);
+            else
+                throw new ThrownException(new Exception());
+        }
+        else if (opt.argObjects().size() == 1 && opt.argObjects().get(0) instanceof Throwable) {
+            throw new ThrownException((Throwable) opt.argObjects().get(0));
+        }
+        else {
+            String message = opt.argObjects().get(0).toString();
+            Throwable cause = null;
+            if (opt.argObjects().size() > 1) {
+                if (opt.argObjects().get(1) instanceof Throwable) {
+                    cause = (Throwable) opt.argObjects().get(1);
+                }
+            }
+            throw new ThrownException(new Exception(message).initCause(cause));
+        }
+    }
+
+    protected Object doTry(CommandSession session, Object[] argv) throws Exception {
+        String[] usage = {
+                "try -  try / catch / finally construct",
+                "Usage: try { try-action } [ { catch-action } [ { finally-action } ]  ]",
+                "  -? --help                    Show help",
+        };
+        Options opt = parseOptions(session, usage, argv);
+        List<Function> functions = getFunctions(opt);
+        if (functions == null || functions.size() < 1 || functions.size() > 3) {
+            System.err.println("usage: try { try-action } [ { catch-action } [ { finally-action } ] ]");
+            session.error(2);
+            return null;
+        }
         try {
-            return func.execute(session, null);
+            return functions.get(0).execute(session, null);
+        } catch (BreakException b) {
+            throw b;
         } catch (Exception e) {
             session.put("exception", e);
-            return error.execute(session, null);
+            if (functions.size() > 1) {
+                functions.get(1).execute(session, null);
+            }
+            return null;
+        } finally {
+            if (functions.size() > 2) {
+                functions.get(2).execute(session, null);
+            }
         }
     }
 
-    public void _while(CommandSession session, Function condition, Function ifTrue)
-            throws Exception {
-        while (isTrue(condition.execute(session, null))) {
-            ifTrue.execute(session, null);
+    protected Object doWhile(CommandSession session, Object[] argv) throws Exception {
+        String[] usage = {
+                "while -  while loop",
+                "Usage: while { condition } { action }",
+                "  -? --help                    Show help",
+        };
+        Options opt = parseOptions(session, usage, argv);
+        List<Function> functions = getFunctions(opt);
+        if (functions == null || functions.size() != 2) {
+            System.err.println("usage: while { condition } { action }");
+            session.error(2);
+            return null;
         }
+        while (isTrue(session, functions.get(0))) {
+            try {
+                functions.get(1).execute(session, null);
+            } catch (BreakException b) {
+                break;
+            }
+        }
+        return null;
     }
 
-    public void until(CommandSession session, Function condition, Function ifTrue)
-            throws Exception {
-        while (!isTrue(condition.execute(session, null))) {
-            ifTrue.execute(session, null);
+    protected Object doUntil(CommandSession session, Object[] argv) throws Exception {
+        String[] usage = {
+                "until -  until loop",
+                "Usage: until { condition } { action }",
+                "  -? --help                    Show help",
+        };
+        Options opt = parseOptions(session, usage, argv);
+        List<Function> functions = new ArrayList<>();
+        for (Object o : opt.argObjects()) {
+            if (o instanceof Function) {
+                functions.add((Function) o);
+            }
+            else {
+                functions = null;
+                break;
+            }
         }
+        int length = opt.argObjects().size();
+        if (length != 2 || functions == null) {
+            System.err.println("usage: until { condition } { action }");
+            session.error(2);
+            return null;
+        }
+        while (!isTrue(session, functions.get(0))) {
+            try {
+                functions.get(1).execute(session, null);
+            } catch (BreakException b) {
+                break;
+            }
+        }
+        return null;
+    }
+
+    protected Object doBreak(CommandSession session, Object[] argv) throws Exception {
+        String[] usage = {
+                "break -  break from loop",
+                "Usage: break",
+                "  -? --help                    Show help",
+        };
+        parseOptions(session, usage, argv);
+        throw new BreakException();
+    }
+
+    private boolean isTrue(CommandSession session, Function function) throws Exception {
+        checkInterrupt();
+        return isTrue(function.execute(session, null));
     }
 
     private boolean isTrue(Object result) throws InterruptedException {
@@ -141,6 +342,35 @@
 
     private void checkInterrupt() throws InterruptedException {
         if (Thread.currentThread().isInterrupted())
-            throw new InterruptedException("loop interrupted");
+            throw new InterruptedException("interrupted");
     }
+
+    @SuppressWarnings("unchecked")
+    private Collection<Object> getElements(Options opt) {
+        Collection<Object> elements = null;
+        if (opt.argObjects().size() > 0) {
+            Object o = opt.argObjects().remove(0);
+            if (o instanceof Collection) {
+                elements = (Collection<Object>) o;
+            } else if (o != null && o.getClass().isArray()) {
+                elements = Arrays.asList((Object[]) o);
+            }
+        }
+        return elements;
+    }
+
+    private List<Function> getFunctions(Options opt) {
+        List<Function> functions = new ArrayList<>();
+        for (Object o : opt.argObjects()) {
+            if (o instanceof Function) {
+                functions.add((Function) o);
+            }
+            else {
+                functions = null;
+                break;
+            }
+        }
+        return functions;
+    }
+
 }
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 8d6e27c..74a3127 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
@@ -35,6 +35,13 @@
     }
 
     @Test
+    public void testLoopBreak() throws Exception {
+        Context context = new Context();
+        Object result = context.execute("$(each {1..10} { i = $it; if { %(i >= 5) } { break } ; echo $i })");
+        Assert.assertEquals("1\n2\n3\n4", result);
+    }
+
+    @Test
     public void testJobIds() throws Exception {
         Context context = new Context();
         // TODO: not than in zsh, the same thing is achieved using
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Reflective.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Reflective.java
index f64d6ea..28e3aa9 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Reflective.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Reflective.java
@@ -63,6 +63,7 @@
         Method[] methods = target.getClass().getMethods();
         name = name.toLowerCase();
 
+        String org = name;
         String get = "get" + name;
         String is = "is" + name;
         String set = "set" + name;
@@ -105,7 +106,7 @@
                 // multiple commands
                 if (mname.equals(MAIN))
                 {
-                    xargs.add(0, name);
+                    xargs.add(0, org);
                 }
 
                 Object[] parms = new Object[types.length];