From 7f49ee09ad6ac1d838153fdee181592fe12c21d5 Mon Sep 17 00:00:00 2001
From: gnembon rect, or range, and maps, where the iterator returns all the map keys
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
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;
From 656638c6dc0b3ee822b77a52550275c36dbfa338 Mon Sep 17 00:00:00 2001
From: gnembon
Date: Fri, 6 Dec 2019 12:24:13 -0400
Subject: [PATCH 2/2] continue and break scarpet functions to replace _exit_
conditions in loops
---
src/main/java/carpet/script/Expression.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java
index a61467c0..31942f11 100644
--- a/src/main/java/carpet/script/Expression.java
+++ b/src/main/java/carpet/script/Expression.java
@@ -1546,7 +1546,7 @@ public class Expression implements Cloneable
* 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.
+ * error. In case of the nexted loops, and more complex setups, use custom try and throw setup.
* 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.