Skip to content

Commit f177274

Browse files
committed
Improve errors displaying for container expressions
1 parent d223bbb commit f177274

File tree

8 files changed

+115
-48
lines changed

8 files changed

+115
-48
lines changed

modules/main/src/main/java/com/annimon/ownlang/modules/std/std_range.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ final class std_range implements Function {
99
public Value execute(Value[] args) {
1010
Arguments.checkRange(1, 3, args.length);
1111
return switch (args.length) {
12-
default -> RangeValue.of(0, getLong(args[0]), 1);
1312
case 2 -> RangeValue.of(getLong(args[0]), getLong(args[1]), 1);
1413
case 3 -> RangeValue.of(getLong(args[0]), getLong(args[1]), getLong(args[2]));
14+
default -> RangeValue.of(0, getLong(args[0]), 1);
1515
};
1616
}
1717

@@ -41,9 +41,13 @@ public RangeValue(long from, long to, long step) {
4141
this.from = from;
4242
this.to = to;
4343
this.step = step;
44-
final long base = (from < to) ? (to - from) : (from - to);
44+
45+
final long base = (from < to)
46+
? Math.subtractExact(to, from)
47+
: Math.subtractExact(from, to);
4548
final long absStep = (step < 0) ? -step : step;
46-
this.size = (int) (base / absStep + (base % absStep == 0 ? 0 : 1));
49+
final long longSize = (base / absStep + (base % absStep == 0 ? 0 : 1));
50+
this.size = longSize > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) longSize;
4751
}
4852

4953
@Override
@@ -67,8 +71,9 @@ public Value[] getCopyElements() {
6771
private boolean isIntegerRange() {
6872
if (to > 0) {
6973
return (from > Integer.MIN_VALUE && to < Integer.MAX_VALUE);
74+
} else {
75+
return (to > Integer.MIN_VALUE && from < Integer.MAX_VALUE);
7076
}
71-
return (to > Integer.MIN_VALUE && from < Integer.MAX_VALUE);
7277
}
7378

7479
@Override
@@ -78,10 +83,12 @@ public int size() {
7883

7984
@Override
8085
public Value get(int index) {
86+
long value = from + index * step;
8187
if (isIntegerRange()) {
82-
return NumberValue.of((int) (from + index * step));
88+
return NumberValue.of((int) (value));
89+
} else {
90+
return NumberValue.of(value);
8391
}
84-
return NumberValue.of(from + (long) index * step);
8592
}
8693

8794
@Override
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
package com.annimon.ownlang.exceptions;
22

3+
import com.annimon.ownlang.util.Range;
4+
35
public final class TypeException extends OwnLangRuntimeException {
46

57
public TypeException(String message) {
68
super(message);
79
}
10+
11+
public TypeException(String message, Range range) {
12+
super(message, range);
13+
}
814
}

ownlang-parser/src/main/java/com/annimon/ownlang/parser/Parser.java

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -353,10 +353,10 @@ private Node functionChain(Node qualifiedNameExpr) {
353353

354354
if (lookMatch(0, TokenType.LPAREN)) {
355355
// next function call
356-
return functionChain(new ContainerAccessExpression(expr, indices));
356+
return functionChain(new ContainerAccessExpression(expr, indices, getRange()));
357357
}
358358
// container access
359-
return new ContainerAccessExpression(expr, indices);
359+
return new ContainerAccessExpression(expr, indices, getRange());
360360
}
361361
return expr;
362362
}
@@ -532,7 +532,7 @@ private AssignmentExpression assignmentStrict() {
532532
final BinaryExpression.Operator op = ASSIGN_OPERATORS.get(currentType);
533533
final Node expression = expression();
534534

535-
return new AssignmentExpression(op, (Accessible) targetExpr, expression);
535+
return new AssignmentExpression(op, (Accessible) targetExpr, expression, getRange(position, index));
536536
}
537537

538538
private Node ternary() {
@@ -765,9 +765,7 @@ private Node objectCreation() {
765765
args.add(expression());
766766
match(TokenType.COMMA);
767767
}
768-
final var expr = new ObjectCreationExpression(className, args);
769-
expr.setRange(getRange(startTokenIndex, index - 1));
770-
return expr;
768+
return new ObjectCreationExpression(className, args, getRange(startTokenIndex, index - 1));
771769
}
772770

773771
return unary();
@@ -859,12 +857,13 @@ private Node qualifiedName() {
859857
if (!match(TokenType.WORD)) return null;
860858

861859
final List<Node> indices = variableSuffix();
860+
final var variable = new VariableExpression(current.text());
861+
variable.setRange(getRange(startTokenIndex, index - 1));
862862
if (indices == null || indices.isEmpty()) {
863-
final var variable = new VariableExpression(current.text());
864-
variable.setRange(getRange(startTokenIndex, index - 1));
865863
return variable;
864+
} else {
865+
return new ContainerAccessExpression(variable, indices, variable.getRange());
866866
}
867-
return new ContainerAccessExpression(current.text(), indices);
868867
}
869868

870869
private List<Node> variableSuffix() {
@@ -905,15 +904,16 @@ private Node value() {
905904
if (lookMatch(1, TokenType.WORD) && lookMatch(2, TokenType.LPAREN)) {
906905
match(TokenType.DOT);
907906
return functionChain(new ContainerAccessExpression(
908-
strExpr, Collections.singletonList(
909-
new ValueExpression(consume(TokenType.WORD).text())
910-
)));
907+
strExpr,
908+
Collections.singletonList(new ValueExpression(consume(TokenType.WORD).text())),
909+
getRange()
910+
));
911911
}
912912
final List<Node> indices = variableSuffix();
913913
if (indices == null || indices.isEmpty()) {
914914
return strExpr;
915915
}
916-
return new ContainerAccessExpression(strExpr, indices);
916+
return new ContainerAccessExpression(strExpr, indices, getRange());
917917
}
918918
return strExpr;
919919
}

ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/AssignmentExpression.java

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,54 @@
11
package com.annimon.ownlang.parser.ast;
22

3+
import com.annimon.ownlang.exceptions.OwnLangRuntimeException;
34
import com.annimon.ownlang.lib.EvaluableValue;
45
import com.annimon.ownlang.lib.Value;
6+
import com.annimon.ownlang.util.Range;
7+
import com.annimon.ownlang.util.SourceLocation;
58

69
/**
710
*
811
* @author aNNiMON
912
*/
10-
public final class AssignmentExpression extends InterruptableNode implements Statement, EvaluableValue {
13+
public final class AssignmentExpression extends InterruptableNode implements Statement, EvaluableValue, SourceLocation {
1114

1215
public final Accessible target;
1316
public final BinaryExpression.Operator operation;
1417
public final Node expression;
18+
private final Range range;
1519

16-
public AssignmentExpression(BinaryExpression.Operator operation, Accessible target, Node expr) {
20+
public AssignmentExpression(BinaryExpression.Operator operation, Accessible target, Node expr, Range range) {
1721
this.operation = operation;
1822
this.target = target;
1923
this.expression = expr;
24+
this.range = range;
25+
}
26+
27+
@Override
28+
public Range getRange() {
29+
return range;
2030
}
2131

2232
@Override
2333
public Value eval() {
2434
super.interruptionCheck();
2535
if (operation == null) {
2636
// Simple assignment
27-
return target.set(expression.eval());
37+
return target.set(checkNonNull(expression.eval(), "Assignment expression"));
2838
}
29-
final Node expr1 = new ValueExpression(target.get());
30-
final Node expr2 = new ValueExpression(expression.eval());
31-
return target.set(new BinaryExpression(operation, expr1, expr2).eval());
39+
final Node expr1 = new ValueExpression(checkNonNull(target.get(), "Assignment target"));
40+
final Node expr2 = new ValueExpression(checkNonNull(expression.eval(), "Assignment expression"));
41+
final Value result = new BinaryExpression(operation, expr1, expr2).eval();
42+
return target.set(result);
3243
}
33-
44+
45+
private Value checkNonNull(Value value, String message) {
46+
if (value == null) {
47+
throw new OwnLangRuntimeException(message + " evaluates to null", range);
48+
}
49+
return value;
50+
}
51+
3452
@Override
3553
public void accept(Visitor visitor) {
3654
visitor.visit(this);

ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/ContainerAccessExpression.java

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,44 @@
11
package com.annimon.ownlang.parser.ast;
22

3+
import com.annimon.ownlang.exceptions.OwnLangRuntimeException;
34
import com.annimon.ownlang.exceptions.TypeException;
45
import com.annimon.ownlang.lib.*;
6+
import com.annimon.ownlang.util.Range;
7+
import com.annimon.ownlang.util.SourceLocation;
58
import java.util.List;
69
import java.util.regex.Pattern;
710

811
/**
912
*
1013
* @author aNNiMON
1114
*/
12-
public final class ContainerAccessExpression implements Node, Accessible {
15+
public final class ContainerAccessExpression implements Node, Accessible, SourceLocation {
1316

1417
private static final Pattern PATTERN_SIMPLE_INDEX = Pattern.compile("^\"[a-zA-Z$_]\\w*\"");
1518

1619
public final Node root;
1720
public final List<Node> indices;
1821
private final boolean[] simpleIndices;
1922
private final boolean rootIsVariable;
23+
private final Range range;
2024

21-
public ContainerAccessExpression(String variable, List<Node> indices) {
22-
this(new VariableExpression(variable), indices);
23-
}
24-
25-
public ContainerAccessExpression(Node root, List<Node> indices) {
25+
public ContainerAccessExpression(Node root, List<Node> indices, Range range) {
2626
rootIsVariable = root instanceof VariableExpression;
2727
this.root = root;
2828
this.indices = indices;
29+
this.range = range;
2930
simpleIndices = precomputeSimpleIndices();
3031
}
3132

3233
public boolean rootIsVariable() {
3334
return rootIsVariable;
3435
}
3536

37+
@Override
38+
public Range getRange() {
39+
return range;
40+
}
41+
3642
public Node getRoot() {
3743
return root;
3844
}
@@ -47,11 +53,24 @@ public Value get() {
4753
final Value container = getContainer();
4854
final Value lastIndex = lastIndex();
4955
return switch (container.type()) {
50-
case Types.ARRAY -> ((ArrayValue) container).get(lastIndex);
56+
case Types.ARRAY -> {
57+
final ArrayValue arr = (ArrayValue) container;
58+
final int size = arr.size();
59+
if (lastIndex.type() != Types.NUMBER) {
60+
yield arr.get(lastIndex);
61+
} else {
62+
final int index = lastIndex.asInt();
63+
if (0 <= index && index < size) {
64+
yield arr.get(index);
65+
} else {
66+
throw outOfBounds(index, size);
67+
}
68+
}
69+
}
5170
case Types.MAP -> ((MapValue) container).get(lastIndex);
5271
case Types.STRING -> ((StringValue) container).access(lastIndex);
5372
case Types.CLASS -> ((ClassInstance) container).access(lastIndex);
54-
default -> throw new TypeException("Array or map expected. Got " + Types.typeToString(container.type()));
73+
default -> throw arrayOrMapExpected(container, " while accessing a container");
5574
};
5675
}
5776

@@ -60,14 +79,23 @@ public Value set(Value value) {
6079
final Value container = getContainer();
6180
final Value lastIndex = lastIndex();
6281
switch (container.type()) {
63-
case Types.ARRAY -> ((ArrayValue) container).set(lastIndex.asInt(), value);
82+
case Types.ARRAY -> {
83+
final ArrayValue arr = (ArrayValue) container;
84+
final int size = arr.size();
85+
final int index = lastIndex.asInt();
86+
if (0 <= index && index < size) {
87+
arr.set(index, value);
88+
} else {
89+
throw outOfBounds(index, size);
90+
}
91+
}
6492
case Types.MAP -> ((MapValue) container).set(lastIndex, value);
6593
case Types.CLASS -> ((ClassInstance) container).set(lastIndex, value);
66-
default -> throw new TypeException("Array or map expected. Got " + container.type());
94+
default -> throw arrayOrMapExpected(container, " while setting a value to container");
6795
}
6896
return value;
6997
}
70-
98+
7199
public Value getContainer() {
72100
Value container = root.eval();
73101
final int last = indices.size() - 1;
@@ -76,7 +104,7 @@ public Value getContainer() {
76104
container = switch (container.type()) {
77105
case Types.ARRAY -> ((ArrayValue) container).get(index.asInt());
78106
case Types.MAP -> ((MapValue) container).get(index);
79-
default -> throw new TypeException("Array or map expected");
107+
default -> throw arrayOrMapExpected(container, " while resolving a container");
80108
};
81109
}
82110
return container;
@@ -96,6 +124,17 @@ public MapValue consumeMap(Value value) {
96124
}
97125
return (MapValue) value;
98126
}
127+
128+
private OwnLangRuntimeException outOfBounds(int index, int size) {
129+
return new OwnLangRuntimeException(
130+
"Index %d is out of bounds for array length %d".formatted(index, size), range);
131+
}
132+
133+
private TypeException arrayOrMapExpected(Value v, String message) {
134+
return new TypeException("Array or map expected"
135+
+ (message == null ? "" : message)
136+
+ ". Got " + Types.typeToString(v.type()), range);
137+
}
99138

100139
@Override
101140
public void accept(Visitor visitor) {

ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/ObjectCreationExpression.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,11 @@ public final class ObjectCreationExpression implements Node, SourceLocation {
1111

1212
public final String className;
1313
public final List<Node> constructorArguments;
14-
private Range range;
14+
private final Range range;
1515

16-
public ObjectCreationExpression(String className, List<Node> constructorArguments) {
16+
public ObjectCreationExpression(String className, List<Node> constructorArguments, Range range) {
1717
this.className = className;
1818
this.constructorArguments = constructorArguments;
19-
}
20-
21-
public void setRange(Range range) {
2219
this.range = range;
2320
}
2421

ownlang-parser/src/main/java/com/annimon/ownlang/parser/optimization/OptimizationVisitor.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public Node visit(AssignmentExpression s, T t) {
3131
final Node exprNode = s.expression.accept(this, t);
3232
final Node targetNode = s.target.accept(this, t);
3333
if ( (exprNode != s.expression || targetNode != s.target) && (targetNode instanceof Accessible) ) {
34-
return new AssignmentExpression(s.operation, (Accessible) targetNode, exprNode);
34+
return new AssignmentExpression(s.operation, (Accessible) targetNode, exprNode, s.getRange());
3535
}
3636
return s;
3737
}
@@ -79,7 +79,7 @@ public Node visit(ClassDeclarationStatement s, T t) {
7979
final AssignmentExpression newField;
8080
if (fieldExpr != field.expression) {
8181
changed = true;
82-
newField = new AssignmentExpression(field.operation, field.target, fieldExpr);
82+
newField = new AssignmentExpression(field.operation, field.target, fieldExpr, field.getRange());
8383
} else {
8484
newField = field;
8585
}
@@ -126,7 +126,7 @@ public Node visit(ContainerAccessExpression s, T t) {
126126
indices.add(node);
127127
}
128128
if (changed) {
129-
return new ContainerAccessExpression(root, indices);
129+
return new ContainerAccessExpression(root, indices, s.getRange());
130130
}
131131
return s;
132132
}
@@ -356,7 +356,7 @@ public Node visit(ObjectCreationExpression s, T t) {
356356
}
357357

358358
if (changed) {
359-
return new ObjectCreationExpression(s.className, args);
359+
return new ObjectCreationExpression(s.className, args, s.getRange());
360360
}
361361
return s;
362362
}

ownlang-parser/src/test/java/com/annimon/ownlang/parser/ast/ASTHelper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public static AssignmentExpression assign(Accessible accessible, Node expr) {
4444
}
4545

4646
public static AssignmentExpression assign(BinaryExpression.Operator op, Accessible accessible, Node expr) {
47-
return new AssignmentExpression(op, accessible, expr);
47+
return new AssignmentExpression(op, accessible, expr, null);
4848
}
4949

5050
public static BinaryExpression operator(BinaryExpression.Operator op, Node left, Node right) {

0 commit comments

Comments
 (0)