diff --git a/src/main/java/com/annimon/ownlang/lib/Variables.java b/src/main/java/com/annimon/ownlang/lib/Variables.java index 4c6f951..4033fa3 100644 --- a/src/main/java/com/annimon/ownlang/lib/Variables.java +++ b/src/main/java/com/annimon/ownlang/lib/Variables.java @@ -48,13 +48,13 @@ public final class Variables { scope.variables.put("false", NumberValue.ZERO); } - static void push() { + public static void push() { synchronized (lock) { scope = new Scope(scope); } } - static void pop() { + public static void pop() { synchronized (lock) { if (scope.parent != null) { scope = scope.parent; diff --git a/src/main/java/com/annimon/ownlang/modules/functional/functional_foreach.java b/src/main/java/com/annimon/ownlang/modules/functional/functional_foreach.java index fc5117e..fc74383 100644 --- a/src/main/java/com/annimon/ownlang/modules/functional/functional_foreach.java +++ b/src/main/java/com/annimon/ownlang/modules/functional/functional_foreach.java @@ -1,36 +1,62 @@ package com.annimon.ownlang.modules.functional; import com.annimon.ownlang.exceptions.TypeException; -import com.annimon.ownlang.lib.Arguments; -import com.annimon.ownlang.lib.ArrayValue; -import com.annimon.ownlang.lib.Function; -import com.annimon.ownlang.lib.MapValue; -import com.annimon.ownlang.lib.Types; -import com.annimon.ownlang.lib.Value; -import com.annimon.ownlang.lib.ValueUtils; +import com.annimon.ownlang.lib.*; import java.util.Map; public final class functional_foreach implements Function { + private static final int UNKNOWN = -1; + @Override public Value execute(Value... args) { Arguments.check(2, args.length); final Value container = args[0]; final Function consumer = ValueUtils.consumeFunction(args[1], 1); - if (container.type() == Types.ARRAY) { - final ArrayValue array = (ArrayValue) container; - for (Value element : array) { - consumer.execute(element); - } - return array; + final int argsCount; + if (consumer instanceof UserDefinedFunction) { + argsCount = ((UserDefinedFunction) consumer).getArgsCount(); + } else { + argsCount = UNKNOWN; } - if (container.type() == Types.MAP) { - final MapValue map = (MapValue) container; - for (Map.Entry element : map) { - consumer.execute(element.getKey(), element.getValue()); - } - return map; + + switch (container.type()) { + case Types.STRING: + final StringValue string = (StringValue) container; + if (argsCount == 2) { + for (char ch : string.asString().toCharArray()) { + consumer.execute(new StringValue(String.valueOf(ch)), NumberValue.of(ch)); + } + } else { + for (char ch : string.asString().toCharArray()) { + consumer.execute(new StringValue(String.valueOf(ch))); + } + } + return string; + + case Types.ARRAY: + final ArrayValue array = (ArrayValue) container; + if (argsCount == 2) { + int index = 0; + for (Value element : array) { + consumer.execute(element, NumberValue.of(index++)); + } + } else { + for (Value element : array) { + consumer.execute(element); + } + } + return array; + + case Types.MAP: + final MapValue map = (MapValue) container; + for (Map.Entry element : map) { + consumer.execute(element.getKey(), element.getValue()); + } + return map; + + default: + throw new TypeException("Cannot iterate " + Types.typeToString(container.type())); } - throw new TypeException("Invalid first argument. Array or map expected"); } } diff --git a/src/main/java/com/annimon/ownlang/parser/ast/ForeachArrayStatement.java b/src/main/java/com/annimon/ownlang/parser/ast/ForeachArrayStatement.java index 640a790..fbc5aa6 100644 --- a/src/main/java/com/annimon/ownlang/parser/ast/ForeachArrayStatement.java +++ b/src/main/java/com/annimon/ownlang/parser/ast/ForeachArrayStatement.java @@ -1,7 +1,8 @@ package com.annimon.ownlang.parser.ast; -import com.annimon.ownlang.lib.Value; -import com.annimon.ownlang.lib.Variables; +import com.annimon.ownlang.exceptions.TypeException; +import com.annimon.ownlang.lib.*; +import java.util.Map; /** * @@ -23,8 +24,45 @@ public final class ForeachArrayStatement extends InterruptableNode implements St public void execute() { super.interruptionCheck(); final Value previousVariableValue = Variables.isExists(variable) ? Variables.get(variable) : null; - final Iterable iterator = (Iterable) container.eval(); - for (Value value : iterator) { + + final Value containerValue = container.eval(); + switch (containerValue.type()) { + case Types.STRING: + iterateString(containerValue.asString()); + break; + case Types.ARRAY: + iterateArray((ArrayValue) containerValue); + break; + case Types.MAP: + iterateMap((MapValue) containerValue); + break; + default: + throw new TypeException("Cannot iterate " + Types.typeToString(containerValue.type())); + } + + // Restore variables + if (previousVariableValue != null) { + Variables.set(variable, previousVariableValue); + } else { + Variables.remove(variable); + } + } + + private void iterateString(String str) { + for (char ch : str.toCharArray()) { + Variables.set(variable, new StringValue(String.valueOf(ch))); + try { + body.execute(); + } catch (BreakStatement bs) { + break; + } catch (ContinueStatement cs) { + // continue; + } + } + } + + private void iterateArray(ArrayValue containerValue) { + for (Value value : containerValue) { Variables.set(variable, value); try { body.execute(); @@ -34,9 +72,21 @@ public final class ForeachArrayStatement extends InterruptableNode implements St // continue; } } - // Восстанавливаем переменную - if (previousVariableValue != null) { - Variables.set(variable, previousVariableValue); + } + + private void iterateMap(MapValue containerValue) { + for (Map.Entry entry : containerValue) { + Variables.set(variable, new ArrayValue(new Value[] { + entry.getKey(), + entry.getValue() + })); + try { + body.execute(); + } catch (BreakStatement bs) { + break; + } catch (ContinueStatement cs) { + // continue; + } } } diff --git a/src/main/java/com/annimon/ownlang/parser/ast/ForeachMapStatement.java b/src/main/java/com/annimon/ownlang/parser/ast/ForeachMapStatement.java index f280561..2a24393 100644 --- a/src/main/java/com/annimon/ownlang/parser/ast/ForeachMapStatement.java +++ b/src/main/java/com/annimon/ownlang/parser/ast/ForeachMapStatement.java @@ -1,7 +1,7 @@ package com.annimon.ownlang.parser.ast; -import com.annimon.ownlang.lib.Value; -import com.annimon.ownlang.lib.Variables; +import com.annimon.ownlang.exceptions.TypeException; +import com.annimon.ownlang.lib.*; import java.util.Map; /** @@ -26,8 +26,66 @@ public final class ForeachMapStatement extends InterruptableNode implements Stat super.interruptionCheck(); final Value previousVariableValue1 = Variables.isExists(key) ? Variables.get(key) : null; final Value previousVariableValue2 = Variables.isExists(value) ? Variables.get(value) : null; - final Iterable> iterator = (Iterable>) container.eval(); - for (Map.Entry entry : iterator) { + + final Value containerValue = container.eval(); + switch (containerValue.type()) { + case Types.STRING: + iterateString(containerValue.asString()); + break; + case Types.ARRAY: + iterateArray((ArrayValue) containerValue); + break; + case Types.MAP: + iterateMap((MapValue) containerValue); + break; + default: + throw new TypeException("Cannot iterate " + Types.typeToString(containerValue.type()) + " as key, value pair"); + } + + // Restore variables + if (previousVariableValue1 != null) { + Variables.set(key, previousVariableValue1); + } else { + Variables.remove(key); + } + if (previousVariableValue2 != null) { + Variables.set(value, previousVariableValue2); + } else { + Variables.remove(value); + } + } + + private void iterateString(String str) { + for (char ch : str.toCharArray()) { + Variables.set(key, new StringValue(String.valueOf(ch))); + Variables.set(value, NumberValue.of(ch)); + try { + body.execute(); + } catch (BreakStatement bs) { + break; + } catch (ContinueStatement cs) { + // continue; + } + } + } + + private void iterateArray(ArrayValue containerValue) { + int index = 0; + for (Value v : containerValue) { + Variables.set(key, v); + Variables.set(value, NumberValue.of(index++)); + try { + body.execute(); + } catch (BreakStatement bs) { + break; + } catch (ContinueStatement cs) { + // continue; + } + } + } + + private void iterateMap(MapValue containerValue) { + for (Map.Entry entry : containerValue) { Variables.set(key, entry.getKey()); Variables.set(value, entry.getValue()); try { @@ -38,15 +96,8 @@ public final class ForeachMapStatement extends InterruptableNode implements Stat // continue; } } - // Восстанавливаем переменные - if (previousVariableValue1 != null) { - Variables.set(key, previousVariableValue1); - } - if (previousVariableValue2 != null) { - Variables.set(value, previousVariableValue2); - } } - + @Override public void accept(Visitor visitor) { visitor.visit(this); diff --git a/src/test/resources/expressions/foreachKeyValue.own b/src/test/resources/expressions/foreachKeyValue.own new file mode 100644 index 0000000..d0256c8 --- /dev/null +++ b/src/test/resources/expressions/foreachKeyValue.own @@ -0,0 +1,30 @@ +def testArrayIterate() { + sum = 0 + for v, i : [1, 2, 3] { + sum += v * i + } + assertEquals(1 * 0 + 2 * 1 + 3 * 2, sum) +} + +def testMapIterate() { + map = {12: 1, 13: 2, 14: 3} + sumKey = 0 + sumValue = 0 + for key, value : map { + sumKey += key + sumValue += value + } + assertEquals(39, sumKey) + assertEquals(6, sumValue) +} + +def testStringIterate() { + str = "" + sum = 0 + for s, code : "abcd" { + str += s.upper + sum += code + } + assertEquals("ABCD", str) + assertEquals(394/*97 + 98 + 99 + 100*/, sum) +} diff --git a/src/test/resources/expressions/foreachValue.own b/src/test/resources/expressions/foreachValue.own new file mode 100644 index 0000000..36942a0 --- /dev/null +++ b/src/test/resources/expressions/foreachValue.own @@ -0,0 +1,41 @@ +use "std" + +def testArrayIterate() { + sum = 0 + for v : [1, 2, 3] { + sum += v + } + assertEquals(6, sum) +} + +def testMapIterate() { + map = {12: 1, 13: 2, 14: 3} + sumKey = 0 + sumValue = 0 + for pair : map { + extract(key, value) = pair + sumKey += key + sumValue += value + } + assertEquals(39, sumKey) + assertEquals(6, sumValue) +} + +def testStringIterate() { + sum = 0 + for s : "abcd" { + sum += s.charAt(0) + } + assertEquals(394/*97 + 98 + 99 + 100*/, sum) +} + +def testScope() { + v = 45 + sum = 0 + for v : [1, 2, 3] { + sum += v + } + assertEquals(6, sum) + assertEquals(45, v) +} + diff --git a/src/test/resources/modules/functional/foreach.own b/src/test/resources/modules/functional/foreach.own new file mode 100644 index 0000000..6d452f4 --- /dev/null +++ b/src/test/resources/modules/functional/foreach.own @@ -0,0 +1,36 @@ +use ["std", "functional"] + +def testArrayForeach1Arg() { + sum = 0 + foreach([1, 2, 3], def(v) { + sum += v + }) + assertEquals(6, sum) +} + +def testArrayForeach2Args() { + sum = 0 + foreach([1, 2, 3], def(v, index) { + sum += v * index + }) + assertEquals(1 * 0 + 2 * 1 + 3 * 2, sum) +} + +def testStringForeach1Arg() { + sum = 0 + foreach("abcd", def(s) { + sum += s.charAt(0) + }) + assertEquals(394/*97 + 98 + 99 + 100*/, sum) +} + +def testStringForeach2Args() { + str = "" + sum = 0 + foreach("abcd", def(s, code) { + str += s.upper + sum += code + }) + assertEquals("ABCD", str) + assertEquals(97 + 98 + 99 + 100, sum) +}