diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java index bed69b33..a61467c0 100644 --- a/src/main/java/carpet/script/Expression.java +++ b/src/main/java/carpet/script/Expression.java @@ -11,11 +11,7 @@ import carpet.script.Fluff.QuadFunction; import carpet.script.Fluff.QuinnFunction; import carpet.script.Fluff.SexFunction; import carpet.script.Fluff.TriFunction; -import carpet.script.exception.ExitStatement; -import carpet.script.exception.ExpressionException; -import carpet.script.exception.InternalExpressionException; -import carpet.script.exception.ReturnStatement; -import carpet.script.exception.ThrowStatement; +import carpet.script.exception.*; import carpet.script.value.AbstractListValue; import carpet.script.value.ContainerValueInterface; import carpet.script.value.FunctionSignatureValue; @@ -1545,21 +1541,33 @@ public class Expression implements Cloneable * generators, like rect, or range, and maps, where the iterator returns all the map keys

*

Loops

* - *

for(list,expr(_,_i),exit(_,_i)?)

- *

Evaluates expression over list of items from the list, and can optionally stop early if - * exit expression is specified and evaluates true for a given iteration. Supplies - * _(value) and _i(iteration number) to both expr and exit. - * Returns the number of times expr was successful

+ *

break(), break(expr), continue(), continue(expr)

+ *

These allow to control execution of a loop either skipping current iteration code, using continue, or finishing + * the current loop, using break. break and continue can only be used inside + * for, while, loop, map, filter and reduce functions, while break can be used in first as well. + * Outside of the internal expressions of these functions, calling break or continue will cause an + * error.

+ *

Please check corresponding loop function description what continue and break do in their contexts, + * but in general case, passed values to break and continue will be used in place of the return value of the + * internal iteration expression.

+ * + * + *

for(list,expr(_,_i))

+ *

Evaluates expression over list of items from the list. Supplies + * _(value) and _i(iteration number) to the expr.

+ *

Returns the number of times expr was successful. Uses continue and break argument + * in place of the returned value from the expr(if supplied), to determine if the iteration was successful.

*
      *     check_prime(n) -> !first( range(2, sqrt(n)+1), !(n % _) );
-     *        for(range(1000000,1100000),check_prime(_))  => 7216
+     *     for(range(1000000,1100000),check_prime(_))  => 7216
      * 
*

From which we can learn that there is 7216 primes between 1M and 1.1M

- *

while(cond, limit, expr)

+ * + *

while(cond, limit)

*

Evaluates expression expr repeatedly until condition cond becomes false, * but not more than limit times. Returns the result of the last expr evaluation, - * or null if nothing was successful. both expr and cond will recveived a - * bound variable _ indicating current iteration, so its a number

+ * or null if nothing was successful. Both expr and cond will recveived a + * bound variable _ indicating current iteration, so its a number.

*
      *  while(a<100,10,a=_*_)  => 81 // loop exhausted via limit
      *  while(a<100,20,a=_*_)  => 100 // loop stopped at condition, but a has already been assigned
@@ -1567,9 +1575,8 @@ public class Expression implements Cloneable
      * 
* *

loop(num,expr(_),exit(_)?)

- *

Evaluates expression expr, num number of times. Optionally, - * if exit condition is present, stops the execution if the condition becomes true. - * Both expr and exit receive _ system variable indicating the iteration

+ *

Evaluates expression expr, num number of times. + *expr receives _ system variable indicating the iteration.

*
      *     loop(5, tick())  => repeat tick 5 times
      *     list = l(); loop(5, x = _; loop(5, list += l(x, _) ) ); list
@@ -1579,25 +1586,27 @@ public class Expression implements Cloneable
      * 
      *     check_prime(n) -> !first( range(2, sqrt(n)+1), !(n % _) );
      *     primes = l();
-     *     loop(10000, if(check_prime(_), primes += _), length(primes) >= 10 );
+     *     loop(10000, if(check_prime(_), primes += _ ; if (length(primes) >= 10, break())));
      *     primes
      *
      *     // outputs: [0, 1, 2, 3, 5, 7, 11, 13, 17, 19]
      * 
+ * *

Higher Order Functions

- *

map(list,expr(_,_i), exit(_,_i)?)

+ *

map(list,expr(_,_i))

*

Converts a list of values, to another list where each value is result of an expression * v = expr(_, _i) where _ is passed as each element of the list, and _i is - * the index of such element, optional exit condition is evaluated after each mapping and if it is false, - * the map is terminated and returned whatever got collected so far

+ * the index of such element. If break is called the map returns whatever collected thus far. If + * continue and break are used with supplied argument, it is used in place of the resulting + * map element, otherwise current element is skipped.

*
      *     map(range(10), _*_)  => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
-     *     map(players('*'), _+' is stupid') [gnembon is stupid, herobrine is stupid]
+     *     map(players('*'), _+' is stoopid') [gnembon is stoopid, herobrine is stoopid]
      * 
* - *

filter(list,expr(_,_i),exit(_,_i))

+ *

filter(list,expr(_,_i))

*

filters list elements returning only these that return positive result of the expr. - * Like with map if the optional condition exit gets true, the list is returned up to this point

+ * With break and continue statements, the supplied value can be used as a boolean check instead.

*
      *     filter(range(100), !(_%5), _*_>1000)  => [0, 5, 10, 15, 20, 25, 30]
      *     map(filter(entities_list('all'),_=='Witch'), query(_,'pos') )  => [[1082.5, 57, 1243.5]]
@@ -1605,7 +1614,8 @@ public class Expression implements Cloneable
      *
      * 

first(list,expr(_,_i))

*

Finds and returns the first item in the list that satisfies expr. It sets _ for current element value, - * and _i for index of that element

+ * and _i for index of that element. break can be called inside the iteration code, using its argument + * value instead of the current item. continue has no sense and cannot be called inside first call.

*
      *     first(range(1000,10000), n=_; !first( range(2, sqrt(n)+1), !(n % _) ) )  => 1009 // first prime after 1000
      * 
@@ -1615,8 +1625,10 @@ public class Expression implements Cloneable * *

all(list,expr(_,_i))

*

Returns true if all elements on the list satisfy the condition. Its roughly equivalent to - * all(list,expr) <=> for(list,expr)==length(list), but works with generators. expr - * also receives bound _ and _i variables

+ * all(list,expr) <=> for(list,expr)==length(list). expr + * also receives bound _ and _i variables. break and continue have no sense + * and cannot be used inside of expr body.

+ * *
      *     all([1,2,3], check_prime(_))  => 1
      *     all(neighbours(x,y,z), _=='stone')  => 1 // if all neighbours of [x, y, z] are stone
@@ -1632,7 +1644,9 @@ public class Expression implements Cloneable
      * 

reduce(list,expr(_a,_,_i), initial)

*

Applies expr for each element of the list and saves the result in _a accumulator. * Consecutive calls to expr can access that value to apply more values. You also need to specify - * the initial value to apply for the accumulator

+ * the initial value to apply for the accumulator. break can be used to terminate reduction prematurely. + * If a value is provided to break or continue, it will be used from now on as a new value for the + * accumulator.

*
      *     reduce([1,2,3,4],_a+_,0)  => 10
      *     reduce([1,2,3,4],_a*_,1)  => 24
@@ -1644,6 +1658,18 @@ public class Expression implements Cloneable
         // condition and expression will get a bound '_i'
         // returns last successful expression or false
         // while(cond, limit, expr) => ??
+        addFunction("break", lv -> {
+            if (lv.size()==0) throw new BreakStatement(null);
+            if (lv.size()==1) throw new BreakStatement(lv.get(0));
+            throw new InternalExpressionException("'break' can only be called with zero or one argument");
+        });
+
+        addFunction("continue", lv -> {
+            if (lv.size()==0) throw new ContinueStatement(null);
+            if (lv.size()==1) throw new ContinueStatement(lv.get(0));
+            throw new InternalExpressionException("'continue' can only be called with zero or one argument");
+        });
+
         addLazyFunction("while", 3, (c, t, lv) ->
         {
             long limit = NumericValue.asNumber(lv.get(1).evalValue(c)).getLong();
@@ -1656,7 +1682,15 @@ public class Expression implements Cloneable
             c.setVariable("_",(cc, tt) -> new NumericValue(0).bindTo("_"));
             while (i new NumericValue(seriously).bindTo("_"));
@@ -1667,29 +1701,28 @@ public class Expression implements Cloneable
             return (cc, tt) -> lastValueNoKidding;
         });
 
-        // loop(Num, expr, exit_condition) => last_value
-        // loop(list, expr)
+        // loop(Num, expr) => last_value
         // expr receives bounded variable '_' indicating iteration
-        addLazyFunction("loop", -1, (c, t, lv) ->
+        addLazyFunction("loop", 2, (c, t, lv) ->
         {
-            if (lv.size()<2 || lv.size()>3)
-            {
-                throw new InternalExpressionException("Incorrect number of attributes for 'loop', should be 2 or 3, not "+lv.size());
-            }
             long limit = NumericValue.asNumber(lv.get(0).evalValue(c)).getLong();
             Value lastOne = Value.NULL;
             LazyValue expr = lv.get(1);
-            LazyValue cond = null;
-            if(lv.size() > 2) cond = lv.get(2);
             //scoping
             LazyValue _val = c.getVariable("_");
             for (long i=0; i < limit; i++)
             {
                 long whyYouAsk = i;
                 c.setVariable("_", (cc, tt) -> new NumericValue(whyYouAsk).bindTo("_"));
-                lastOne = expr.evalValue(c, t);
-                if (cond != null && cond.evalValue(c, Context.BOOLEAN).getBoolean())
-                    break;
+                try
+                {
+                    lastOne = expr.evalValue(c, t);
+                }
+                catch (BreakStatement | ContinueStatement stmt)
+                {
+                    if (stmt.retval != null) lastOne = stmt.retval;
+                    if (stmt instanceof BreakStatement) break;
+                }
             }
             //revering scope
             c.setVariable("_", _val);
@@ -1699,21 +1732,13 @@ public class Expression implements Cloneable
 
         // map(list or Num, expr) => list_results
         // receives bounded variable '_' with the expression
-        addLazyFunction("map", -1, (c, t, lv) ->
+        addLazyFunction("map", 2, (c, t, lv) ->
         {
-            if (lv.size()<2 || lv.size()>3)
-            {
-                throw new InternalExpressionException("Incorrect number of attributes for 'map', should be 2 or 3, not "+lv.size());
-            }
-
             Value rval= lv.get(0).evalValue(c);
-
             if (!(rval instanceof AbstractListValue))
                 throw new InternalExpressionException("First argument of 'map' function should be a list or iterator");
             Iterator iterator = ((AbstractListValue) rval).iterator();
             LazyValue expr = lv.get(1);
-            LazyValue cond = null;
-            if(lv.size() > 2) cond = lv.get(2);
             //scoping
             LazyValue _val = c.getVariable("_");
             LazyValue _iter = c.getVariable("_i");
@@ -1726,11 +1751,18 @@ public class Expression implements Cloneable
                 int doYouReally = i;
                 c.setVariable("_", (cc, tt) -> next);
                 c.setVariable("_i", (cc, tt) -> new NumericValue(doYouReally).bindTo("_i"));
-                result.add(expr.evalValue(c));
-                if (cond != null && cond.evalValue(c, Context.BOOLEAN).getBoolean())
+                try
                 {
-                    next.boundVariable = var;
-                    break;
+                    result.add(expr.evalValue(c));
+                }
+                catch (BreakStatement | ContinueStatement stmt)
+                {
+                    if (stmt.retval != null) result.add(stmt.retval);
+                    if (stmt instanceof BreakStatement)
+                    {
+                        next.boundVariable = var;
+                        break;
+                    }
                 }
                 next.boundVariable = var;
             }
@@ -1742,23 +1774,16 @@ public class Expression implements Cloneable
             return (cc, tt) ->  ret;
         });
 
-        // grep(list or num, expr, exit_expr) => list
+        // grep(list or num, expr) => list
         // receives bounded variable '_' with the expression, and "_i" with index
         // produces list of values for which the expression is true
-        addLazyFunction("filter", -1, (c, t, lv) ->
+        addLazyFunction("filter", 2, (c, t, lv) ->
         {
-            if (lv.size()<2 || lv.size()>3)
-            {
-                throw new InternalExpressionException("Incorrect number of attributes for 'filter', should be 2 or 3, not "+lv.size());
-            }
-
             Value rval= lv.get(0).evalValue(c);
             if (!(rval instanceof AbstractListValue))
                 throw new InternalExpressionException("First argument of 'filter' function should be a list or iterator");
             Iterator iterator = ((AbstractListValue) rval).iterator();
             LazyValue expr = lv.get(1);
-            LazyValue cond = null;
-            if(lv.size() > 2) cond = lv.get(2);
             //scoping
             LazyValue _val = c.getVariable("_");
             LazyValue _iter = c.getVariable("_i");
@@ -1771,12 +1796,19 @@ public class Expression implements Cloneable
                 int seriously = i;
                 c.setVariable("_", (cc, tt) -> next);
                 c.setVariable("_i", (cc, tt) -> new NumericValue(seriously).bindTo("_i"));
-                if(expr.evalValue(c, Context.BOOLEAN).getBoolean())
-                    result.add(next);
-                if (cond != null && cond.evalValue(c, Context.BOOLEAN).getBoolean())
+                try
                 {
-                    next.boundVariable = var;
-                    break;
+                    if(expr.evalValue(c, Context.BOOLEAN).getBoolean())
+                        result.add(next);
+                }
+                catch (BreakStatement | ContinueStatement stmt)
+                {
+                    if (stmt.retval != null && stmt.retval.getBoolean()) result.add(next);
+                    if (stmt instanceof BreakStatement)
+                    {
+                        next.boundVariable = var;
+                        break;
+                    }
                 }
                 next.boundVariable = var;
             }
@@ -1811,12 +1843,25 @@ public class Expression implements Cloneable
                 int seriously = i;
                 c.setVariable("_", (cc, tt) -> next);
                 c.setVariable("_i", (cc, tt) -> new NumericValue(seriously).bindTo("_i"));
-                if(expr.evalValue(c, Context.BOOLEAN).getBoolean())
+                try
                 {
-                    result = next;
+                    if(expr.evalValue(c, Context.BOOLEAN).getBoolean())
+                    {
+                        result = next;
+                        next.boundVariable = var;
+                        break;
+                    }
+                }
+                catch (BreakStatement  stmt)
+                {
+                    result = stmt.retval == null? next : stmt.retval;
                     next.boundVariable = var;
                     break;
                 }
+                catch (ContinueStatement ignored)
+                {
+                    throw new InternalExpressionException("'continue' inside 'first' function has no sense");
+                }
                 next.boundVariable = var;
             }
             //revering scope
@@ -1865,22 +1910,15 @@ public class Expression implements Cloneable
         });
 
         // similar to map, but returns total number of successes
-        // for(list, expr, exit_expr) => success_count
+        // for(list, expr) => success_count
         // can be substituted for first and all, but first is more efficient and all doesn't require knowing list size
         addLazyFunction("for", -1, (c, t, lv) ->
         {
-            if (lv.size()<2 || lv.size()>3)
-            {
-                throw new InternalExpressionException("Incorrect number of attributes for 'for', should be 2 or 3, not "+lv.size());
-            }
             Value rval= lv.get(0).evalValue(c);
             if (!(rval instanceof AbstractListValue))
                 throw new InternalExpressionException("Second argument of 'for' function should be a list or iterator");
             Iterator iterator = ((AbstractListValue) rval).iterator();
             LazyValue expr = lv.get(1);
-            LazyValue cond = null;
-            if(lv.size() > 2) cond = lv.get(2);
-
             //scoping
             LazyValue _val = c.getVariable("_");
             LazyValue _iter = c.getVariable("_i");
@@ -1893,14 +1931,22 @@ public class Expression implements Cloneable
                 int seriously = i;
                 c.setVariable("_", (cc, tt) -> next);
                 c.setVariable("_i", (cc, tt) -> new NumericValue(seriously).bindTo("_i"));
-                Value result = expr.evalValue(c, t);
+                Value result = Value.FALSE;
+                try
+                {
+                    result = expr.evalValue(c, t);
+                }
+                catch (BreakStatement | ContinueStatement stmt)
+                {
+                    if (stmt.retval != null) result = stmt.retval;
+                    if (stmt instanceof BreakStatement)
+                    {
+                        next.boundVariable = var;
+                        break;
+                    }
+                }
                 if(t != Context.VOID && result.getBoolean())
                     successCount++;
-                if (cond != null && cond.evalValue(c, Context.BOOLEAN).getBoolean())
-                {
-                    next.boundVariable = var;
-                    break;
-                }
                 next.boundVariable = var;
             }
             //revering scope
@@ -1944,7 +1990,19 @@ public class Expression implements Cloneable
                 Value promiseWontChangeYou = acc;
                 c.setVariable("_a", (cc, tt) -> promiseWontChangeYou.bindTo("_a"));
                 c.setVariable("_", (cc, tt) -> next);
-                acc = expr.evalValue(c, t);
+                try
+                {
+                    acc = expr.evalValue(c, t);
+                }
+                catch (BreakStatement | ContinueStatement stmt)
+                {
+                    if (stmt.retval != null) acc = stmt.retval;
+                    if (stmt instanceof BreakStatement)
+                    {
+                        next.boundVariable = var;
+                        break;
+                    }
+                }
                 next.boundVariable = var;
             }
             //reverting scope
@@ -3140,9 +3198,13 @@ public class Expression implements Cloneable
         {
             return exprProvider.get().evalValue(c, expectedType);
         }
+        catch (ContinueStatement | BreakStatement | ReturnStatement exc)
+        {
+            throw new ExpressionException("Control flow functions, like continue, break, or return, should only be used in loops and functions respectively.");
+        }
         catch (ExitStatement exit)
         {
-            return exit.retval;
+            return exit.retval==null?Value.NULL:exit.retval;
         }
         catch (StackOverflowError ignored)
         {
diff --git a/src/main/java/carpet/script/exception/BreakStatement.java b/src/main/java/carpet/script/exception/BreakStatement.java
new file mode 100644
index 00000000..8705d124
--- /dev/null
+++ b/src/main/java/carpet/script/exception/BreakStatement.java
@@ -0,0 +1,11 @@
+package carpet.script.exception;
+
+import carpet.script.value.Value;
+
+public class BreakStatement extends ExitStatement
+{
+    public BreakStatement(Value value)
+    {
+        super(value);
+    }
+}
diff --git a/src/main/java/carpet/script/exception/ContinueStatement.java b/src/main/java/carpet/script/exception/ContinueStatement.java
new file mode 100644
index 00000000..f31c5af3
--- /dev/null
+++ b/src/main/java/carpet/script/exception/ContinueStatement.java
@@ -0,0 +1,11 @@
+package carpet.script.exception;
+
+import carpet.script.value.Value;
+
+public class ContinueStatement extends ExitStatement
+{
+    public ContinueStatement(Value value)
+    {
+        super(value);
+    }
+}
diff --git a/src/main/java/carpet/script/value/FunctionValue.java b/src/main/java/carpet/script/value/FunctionValue.java
index 492c2728..0c1e59a7 100644
--- a/src/main/java/carpet/script/value/FunctionValue.java
+++ b/src/main/java/carpet/script/value/FunctionValue.java
@@ -6,6 +6,8 @@ import carpet.script.ExpressionInspector;
 import carpet.script.Fluff;
 import carpet.script.LazyValue;
 import carpet.script.Tokenizer;
+import carpet.script.exception.BreakStatement;
+import carpet.script.exception.ContinueStatement;
 import carpet.script.exception.ExitStatement;
 import carpet.script.exception.ExpressionException;
 import carpet.script.exception.InternalExpressionException;
@@ -164,6 +166,10 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction
         {
             retVal = body.evalValue(newFrame, type); // todo not sure if we need to propagete type / consider boolean context in defined functions - answer seems ye
         }
+        catch (BreakStatement | ContinueStatement exc)
+        {
+            throw new ExpressionException(e, t, "'continue' and 'break' can only be called inside loop function bodies");
+        }
         catch (ReturnStatement returnStatement)
         {
             retVal = returnStatement.retval;