mirror of
https://github.com/aNNiMON/Own-Programming-Language-Tutorial.git
synced 2024-09-20 00:34:20 +03:00
Fix scope of foreach and match statements
v = 100 for v : [1] .. v should be 1, not 100
This commit is contained in:
parent
b7c376f01f
commit
32bffaee86
@ -0,0 +1,8 @@
|
|||||||
|
package com.annimon.ownlang.lib;
|
||||||
|
|
||||||
|
public final class AutoCloseableScope implements AutoCloseable {
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
ScopeHandler.pop();
|
||||||
|
}
|
||||||
|
}
|
@ -36,6 +36,11 @@ public final class ScopeHandler {
|
|||||||
scope = rootScope;
|
scope = rootScope;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static AutoCloseableScope closeableScope() {
|
||||||
|
push();
|
||||||
|
return new AutoCloseableScope();
|
||||||
|
}
|
||||||
|
|
||||||
public static void push() {
|
public static void push() {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
scope = new Scope(scope);
|
scope = new Scope(scope);
|
||||||
|
@ -22,7 +22,7 @@ public final class Parser {
|
|||||||
final Parser parser = new Parser(tokens);
|
final Parser parser = new Parser(tokens);
|
||||||
final Statement program = parser.parse();
|
final Statement program = parser.parse();
|
||||||
if (parser.getParseErrors().hasErrors()) {
|
if (parser.getParseErrors().hasErrors()) {
|
||||||
throw new ParseException();
|
throw new ParseException(parser.getParseErrors().toString());
|
||||||
}
|
}
|
||||||
return program;
|
return program;
|
||||||
}
|
}
|
||||||
|
@ -23,22 +23,14 @@ public final class ForeachArrayStatement extends InterruptableNode implements St
|
|||||||
@Override
|
@Override
|
||||||
public void execute() {
|
public void execute() {
|
||||||
super.interruptionCheck();
|
super.interruptionCheck();
|
||||||
// TODO removing without checking shadowing is dangerous
|
try (final var ignored = ScopeHandler.closeableScope()) {
|
||||||
final Value previousVariableValue = ScopeHandler.getVariable(variable);
|
final Value containerValue = container.eval();
|
||||||
|
switch (containerValue.type()) {
|
||||||
final Value containerValue = container.eval();
|
case Types.STRING -> iterateString(containerValue.asString());
|
||||||
switch (containerValue.type()) {
|
case Types.ARRAY -> iterateArray((ArrayValue) containerValue);
|
||||||
case Types.STRING -> iterateString(containerValue.asString());
|
case Types.MAP -> iterateMap((MapValue) containerValue);
|
||||||
case Types.ARRAY -> iterateArray((ArrayValue) containerValue);
|
default -> throw new TypeException("Cannot iterate " + Types.typeToString(containerValue.type()));
|
||||||
case Types.MAP -> iterateMap((MapValue) containerValue);
|
}
|
||||||
default -> throw new TypeException("Cannot iterate " + Types.typeToString(containerValue.type()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore variables
|
|
||||||
if (previousVariableValue != null) {
|
|
||||||
ScopeHandler.setVariable(variable, previousVariableValue);
|
|
||||||
} else {
|
|
||||||
ScopeHandler.removeVariable(variable);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,28 +24,14 @@ public final class ForeachMapStatement extends InterruptableNode implements Stat
|
|||||||
@Override
|
@Override
|
||||||
public void execute() {
|
public void execute() {
|
||||||
super.interruptionCheck();
|
super.interruptionCheck();
|
||||||
// TODO removing without checking shadowing is dangerous
|
try (final var ignored = ScopeHandler.closeableScope()) {
|
||||||
final Value previousVariableValue1 = ScopeHandler.getVariable(key);
|
final Value containerValue = container.eval();
|
||||||
final Value previousVariableValue2 = ScopeHandler.getVariable(value);
|
switch (containerValue.type()) {
|
||||||
|
case Types.STRING -> iterateString(containerValue.asString());
|
||||||
final Value containerValue = container.eval();
|
case Types.ARRAY -> iterateArray((ArrayValue) containerValue);
|
||||||
switch (containerValue.type()) {
|
case Types.MAP -> iterateMap((MapValue) containerValue);
|
||||||
case Types.STRING -> iterateString(containerValue.asString());
|
default -> throw new TypeException("Cannot iterate " + Types.typeToString(containerValue.type()) + " as key, value pair");
|
||||||
case Types.ARRAY -> iterateArray((ArrayValue) containerValue);
|
}
|
||||||
case Types.MAP -> iterateMap((MapValue) containerValue);
|
|
||||||
default -> throw new TypeException("Cannot iterate " + Types.typeToString(containerValue.type()) + " as key, value pair");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore variables
|
|
||||||
if (previousVariableValue1 != null) {
|
|
||||||
ScopeHandler.setVariable(key, previousVariableValue1);
|
|
||||||
} else {
|
|
||||||
ScopeHandler.removeVariable(key);
|
|
||||||
}
|
|
||||||
if (previousVariableValue2 != null) {
|
|
||||||
ScopeHandler.setVariable(value, previousVariableValue2);
|
|
||||||
} else {
|
|
||||||
ScopeHandler.removeVariable(value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,13 +54,10 @@ public final class MatchExpression extends InterruptableNode implements Expressi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((value.type() == Types.ARRAY) && (p instanceof ListPattern pattern)) {
|
if ((value.type() == Types.ARRAY) && (p instanceof ListPattern pattern)) {
|
||||||
if (matchListPattern((ArrayValue) value, pattern)) {
|
try (final var ignored = ScopeHandler.closeableScope()) {
|
||||||
// Clean up variables if matched
|
if (matchListPattern((ArrayValue) value, pattern)) {
|
||||||
final Value result = evalResult(p.result);
|
return evalResult(p.result);
|
||||||
for (String var : pattern.parts) {
|
|
||||||
ScopeHandler.removeVariable(var);
|
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((value.type() == Types.ARRAY) && (p instanceof TuplePattern pattern)) {
|
if ((value.type() == Types.ARRAY) && (p instanceof TuplePattern pattern)) {
|
||||||
@ -91,20 +88,12 @@ public final class MatchExpression extends InterruptableNode implements Expressi
|
|||||||
final int arraySize = array.size();
|
final int arraySize = array.size();
|
||||||
switch (partsSize) {
|
switch (partsSize) {
|
||||||
case 0: // match [] { case []: ... }
|
case 0: // match [] { case []: ... }
|
||||||
if ((arraySize == 0) && optMatches(p)) {
|
return (arraySize == 0) && optMatches(p);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
|
|
||||||
case 1: // match arr { case [x]: x = arr ... }
|
case 1: // match arr { case [x]: x = arr ... }
|
||||||
final String variable = parts.get(0);
|
final String variable = parts.get(0);
|
||||||
ScopeHandler.defineVariableInCurrentScope(variable, array);
|
ScopeHandler.defineVariableInCurrentScope(variable, array);
|
||||||
if (optMatches(p)) {
|
return optMatches(p);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// TODO remove is dangerous
|
|
||||||
ScopeHandler.removeVariable(variable);
|
|
||||||
return false;
|
|
||||||
|
|
||||||
default: { // match arr { case [...]: .. }
|
default: { // match arr { case [...]: .. }
|
||||||
if (partsSize == arraySize) {
|
if (partsSize == arraySize) {
|
||||||
@ -124,16 +113,7 @@ public final class MatchExpression extends InterruptableNode implements Expressi
|
|||||||
for (int i = 0; i < partsSize; i++) {
|
for (int i = 0; i < partsSize; i++) {
|
||||||
ScopeHandler.defineVariableInCurrentScope(parts.get(i), array.get(i));
|
ScopeHandler.defineVariableInCurrentScope(parts.get(i), array.get(i));
|
||||||
}
|
}
|
||||||
if (optMatches(p)) {
|
return optMatches(p);
|
||||||
// Clean up will be provided after evaluate result
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Clean up variables if no match
|
|
||||||
for (String var : parts) {
|
|
||||||
// TODO removing without checking shadowing is dangerous
|
|
||||||
ScopeHandler.removeVariable(var);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean matchListPatternWithTail(ListPattern p, List<String> parts, int partsSize, ArrayValue array, int arraySize) {
|
private boolean matchListPatternWithTail(ListPattern p, List<String> parts, int partsSize, ArrayValue array, int arraySize) {
|
||||||
@ -148,16 +128,7 @@ public final class MatchExpression extends InterruptableNode implements Expressi
|
|||||||
tail.set(i - lastPart, array.get(i));
|
tail.set(i - lastPart, array.get(i));
|
||||||
}
|
}
|
||||||
ScopeHandler.defineVariableInCurrentScope(parts.get(lastPart), tail);
|
ScopeHandler.defineVariableInCurrentScope(parts.get(lastPart), tail);
|
||||||
// Check optional condition
|
return optMatches(p);
|
||||||
if (optMatches(p)) {
|
|
||||||
// Clean up will be provided after evaluate result
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Clean up variables
|
|
||||||
for (String var : parts) {
|
|
||||||
ScopeHandler.removeVariable(var);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean match(Value value, Value constant) {
|
private boolean match(Value value, Value constant) {
|
||||||
|
@ -57,6 +57,14 @@ public class ProgramsTest {
|
|||||||
() -> ((FunctionValue) args[0]).getValue().execute());
|
() -> ((FunctionValue) args[0]).getValue().execute());
|
||||||
return NumberValue.ONE;
|
return NumberValue.ONE;
|
||||||
});
|
});
|
||||||
|
ScopeHandler.setFunction("fail", (args) -> {
|
||||||
|
if (args.length > 0) {
|
||||||
|
fail(args[0].asString());
|
||||||
|
} else {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
return NumberValue.ONE;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package com.annimon.ownlang.parser.ast;
|
package com.annimon.ownlang.parser.ast;
|
||||||
|
|
||||||
|
import com.annimon.ownlang.lib.ScopeHandler;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import com.annimon.ownlang.exceptions.VariableDoesNotExistsException;
|
import com.annimon.ownlang.exceptions.VariableDoesNotExistsException;
|
||||||
|
|
||||||
@ -11,6 +13,11 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
|||||||
* @author aNNiMON
|
* @author aNNiMON
|
||||||
*/
|
*/
|
||||||
public class VariableExpressionTest {
|
public class VariableExpressionTest {
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
ScopeHandler.resetScope();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testVariable() {
|
public void testVariable() {
|
||||||
|
@ -28,3 +28,16 @@ def testStringIterate() {
|
|||||||
assertEquals("ABCD", str)
|
assertEquals("ABCD", str)
|
||||||
assertEquals(394/*97 + 98 + 99 + 100*/, sum)
|
assertEquals(394/*97 + 98 + 99 + 100*/, sum)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def testScope() {
|
||||||
|
a = 100
|
||||||
|
b = 200
|
||||||
|
sum = 0
|
||||||
|
for a, b : {14: 3} {
|
||||||
|
sum += a
|
||||||
|
sum += b
|
||||||
|
}
|
||||||
|
assertEquals(17, sum)
|
||||||
|
assertEquals(14, a)
|
||||||
|
assertEquals(3, b)
|
||||||
|
}
|
@ -36,6 +36,6 @@ def testScope() {
|
|||||||
sum += v
|
sum += v
|
||||||
}
|
}
|
||||||
assertEquals(6, sum)
|
assertEquals(6, sum)
|
||||||
assertEquals(45, v)
|
assertEquals(3, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,179 @@
|
|||||||
|
use "types"
|
||||||
|
|
||||||
|
def testMatchValue() {
|
||||||
|
value = 20
|
||||||
|
result = match value {
|
||||||
|
case 10: "ten"
|
||||||
|
case 20: "twenty"
|
||||||
|
}
|
||||||
|
assertEquals("twenty", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
def testMatchValueAny() {
|
||||||
|
value = 20
|
||||||
|
result = match value {
|
||||||
|
case 0: "zero"
|
||||||
|
case 1: "one"
|
||||||
|
case _: "other"
|
||||||
|
}
|
||||||
|
assertEquals("other", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
def testMatchAdditionalCheck() {
|
||||||
|
value = 20
|
||||||
|
result = match value {
|
||||||
|
case 10: "ten"
|
||||||
|
case x if x < 10: "" + x + "<10"
|
||||||
|
case x if x > 10: "" + x + ">10"
|
||||||
|
}
|
||||||
|
assertEquals("20>10", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
def testMatchAdditionalCheckScope() {
|
||||||
|
x = 20
|
||||||
|
result = match x {
|
||||||
|
case 10: "ten"
|
||||||
|
case x if x < 10: fail()
|
||||||
|
case y if y > 10: assertEquals(20, y)
|
||||||
|
}
|
||||||
|
assertEquals(20, x)
|
||||||
|
assertEquals(true, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
def printArrayRecursive(arr) = match arr {
|
||||||
|
case [head :: tail]: "[" + head + ", " + printArrayRecursive(tail) + "]"
|
||||||
|
case []: "[]"
|
||||||
|
case last: "[" + last + ", []]"
|
||||||
|
}
|
||||||
|
|
||||||
|
def testMatchEmptyArray() {
|
||||||
|
result = printArrayRecursive([])
|
||||||
|
assertEquals("[]", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
def testMatchOneElementArray() {
|
||||||
|
result = printArrayRecursive([1])
|
||||||
|
assertEquals("[[1], []]", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
def testMatchTwoElementsArray() {
|
||||||
|
result = printArrayRecursive([1, 2])
|
||||||
|
assertEquals("[1, [2, []]]", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
def testMatchArray() {
|
||||||
|
result = printArrayRecursive([1, 2, 3, 4])
|
||||||
|
assertEquals("[1, [2, [3, [4, []]]]]", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
def testMatchArray2() {
|
||||||
|
def elementsCount(arr) = match arr {
|
||||||
|
case [a :: b :: c :: d :: e]: 5
|
||||||
|
case [a :: b :: c :: d]: 4
|
||||||
|
case [a :: b :: c]: 3
|
||||||
|
case [a :: b]: 2
|
||||||
|
case (7): -7 // special case 1
|
||||||
|
case [a] if a == [8]: -8 // special case 2
|
||||||
|
case []: 0
|
||||||
|
case [a]: 1
|
||||||
|
}
|
||||||
|
assertEquals(4, elementsCount([1, 2, 3, 4]))
|
||||||
|
assertEquals(3, elementsCount([1, 2, 3]))
|
||||||
|
assertEquals(2, elementsCount([1, 2]))
|
||||||
|
assertEquals(1, elementsCount([1]))
|
||||||
|
assertEquals(-7, elementsCount([7]))
|
||||||
|
assertEquals(-8, elementsCount([8]))
|
||||||
|
assertEquals(0, elementsCount([]))
|
||||||
|
}
|
||||||
|
|
||||||
|
def testMatchOneElementArrayScope() {
|
||||||
|
head = 100
|
||||||
|
tail = 200
|
||||||
|
result = match [1] {
|
||||||
|
case [head :: tail]: fail("Multi-array")
|
||||||
|
case []: fail("Empty array")
|
||||||
|
case last: assertEquals(1, last[0])
|
||||||
|
}
|
||||||
|
assertEquals(100, head)
|
||||||
|
assertEquals(200, tail)
|
||||||
|
assertEquals(true, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
def testMatchOneElementArrayDefinedVariableScope() {
|
||||||
|
head = 100
|
||||||
|
tail = 200
|
||||||
|
last = 300
|
||||||
|
result = match [1] {
|
||||||
|
case [head :: tail]: fail("Multi-array")
|
||||||
|
case []: fail("Empty array")
|
||||||
|
case last: fail("Array should not be equal " + last)
|
||||||
|
case rest: assertEquals(1, rest[0])
|
||||||
|
}
|
||||||
|
assertEquals(100, head)
|
||||||
|
assertEquals(200, tail)
|
||||||
|
assertEquals(300, last)
|
||||||
|
assertEquals(true, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
def testMatchArrayScope() {
|
||||||
|
head = 100
|
||||||
|
tail = 200
|
||||||
|
result = match [1, 2, 3] {
|
||||||
|
case [head :: tail]: assertEquals(1, head)
|
||||||
|
case []: fail("Empty array")
|
||||||
|
case last: fail("One element")
|
||||||
|
}
|
||||||
|
assertEquals(100, head)
|
||||||
|
assertEquals(200, tail)
|
||||||
|
assertEquals(true, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
def testMatchTuple() {
|
||||||
|
result = match [1, 2] {
|
||||||
|
case (0, 1): "(0, 1)"
|
||||||
|
case (1, 2): "(1, 2)"
|
||||||
|
case (2, 3): "(2, 3)"
|
||||||
|
}
|
||||||
|
assertEquals("(1, 2)", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
def testMatchTupleDifferentLength() {
|
||||||
|
result = match [1, 2] {
|
||||||
|
case (1): "(1)"
|
||||||
|
case (1, 2, 3, 4): "(1, 2, 3, 4)"
|
||||||
|
case _: "not matched"
|
||||||
|
}
|
||||||
|
assertEquals("not matched", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
def testMatchTupleAny1() {
|
||||||
|
result = match [1, 2] {
|
||||||
|
case (0, _): "(0, _)"
|
||||||
|
case (1, _): "(1, _)"
|
||||||
|
case (2, _): "(2, _)"
|
||||||
|
}
|
||||||
|
assertEquals("(1, _)", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
def testMatchTupleAny2() {
|
||||||
|
result = match [2, 3] {
|
||||||
|
case (0, _): "(0, _)"
|
||||||
|
case (1, _): "(1, _)"
|
||||||
|
case (_, _): "(_, _)"
|
||||||
|
}
|
||||||
|
assertEquals("(_, _)", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
def testMatchTupleAny3() {
|
||||||
|
result = match [2, 3] {
|
||||||
|
case (0, _): "(0, _)"
|
||||||
|
case (1, _): "(1, _)"
|
||||||
|
case _: "_"
|
||||||
|
}
|
||||||
|
assertEquals("_", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
def testScope() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user