added trace information to thrown exceptions and clean some of the docs

This commit is contained in:
gnembon committed 2021-03-09 04:57:23 -04:00
1 parent d48f78ff7b
commit d72a617290
5 files changed
+99 -55

No files matched your search

@@ -234,61 +234,71 @@ returns everywhere, but it would often lead to a messy code.
It terminates entire program passing `expr` as the result of the program execution, or null if omitted. It terminates entire program passing `expr` as the result of the program execution, or null if omitted.
### `try(expr, id_filter, catch_expr(_, _msg), id_filter2?, catch_expr2(_, _msg), ...)` ### `try(expr)` `try(expr, user_catch_expr)` `try(expr, type, catch_expr, type?, catch_expr?, ...)`
`try` function evaluates expression, and continues further unless an exception is thrown anywhere inside `expr`, `try` evaluates expression, allowing capturing exceptions that would be thrown inside `expr` statement. The exceptions can be
it being produced by `throw` or a catchable exception being thrown by Carpet. In that case the `catch_expr` is evaluated with thrown explicitly using `throw()` or internally by scarpet where code is correct but detects illegal state. The 2-argument form
`_` set to the id of the exception and `_msg` set to the exception message. Only exceptions with their or their parent's id(recursively) equal catches only user-thrown exceptions and one argument call `try(expr)` is equivalent to `try(expr, null)`,
to a provided `id_filter` will be caught, and any other exception will continue up the stack. You can specify multiple filters or `try(expr, 'user_exception', null)`. If multiple `type-catch` pairs are defined, the execution terminates on the first
and their respective `catch_expr` to be able to catch different exceptions within the same code. When an exception occurs, the script applicable type for the exception thrown. Therefore, even if the caught exception matches multiple filters, only
will try to find an equality with every provided filter in order. Therefore, even if the caught exception matches multiple filters, only the first matching block will be executed.
the first matching block will be executed. It is recommended to filter the exceptions to catch to be able to distinguish from an exception
that is known that may happen from one that shouldn't.
This mechanism allows to terminate large portion of a convoluted call stack and continue program execution. Catch expressions are evaluated with
`_` set to the value associated with the exception and `_trace` set to contain details about point of error (token, and line and
column positions), call stack and local
variables at the time of failure. The `type` will catch any exception of that type and any subtype of this type.
The `try` function allows you to catch some Scarpet exceptions for those cases when trying to get things like items,
blocks, biomes or dimensions from registries, that may have been modified by datapacks, resourcepacks or other mods, or when an error that
is out of the scope of the programmer occurs, such as problems when reading files.
Deprecated usages: You can use `try` mechanism to exit from large portion of a convoluted call stack and continue program execution, although catching
- `try(expr)`: Filter the exception you are looking for, and provide something as the `catch_expr` (use `null` if you have nothing to provide). exceptions is typically much more expensive comparing to not throwing them.
Example replacement: `try(expr, 'exception', null)`
- `try(expr, catch_expr)`: Filter the exception you are looking for.
In this documentation, those are documented in at least most of the functions that throw them, and the current exception The `try` function allows you to catch some scarpet exceptions for cases covering invalid data, like invalid
hierarchy is the following: blocks, biomes, dimensions and other things, that may have been modified by datapacks, resourcepacks or other mods,
- `exception`: This is the base exception. Every exception has this as its top-level parent or when an error is outside of the programmers scope, such as problems when reading or decoding files.
- `value_exception`: This is the parent for any exception that occurs due to a provided value for something not being present in the game
- `unknown_item`: Happens when a specified item doesn't exist
- `unknown_block`: Happens when a specified block doesn't exist
- `unknown_biome`: Happens when a specified biome doesn't exist
- `unknown_sound`: Happens when a specified sound doesn't exist
- `unknown_particle`: Happens when a specified particle doesn't exist
- `unknown_poi_type`: Happens when a specified POI type doesn't exist
- `unknown_dimension`: Happens when a specified dimension doesn't exist
- `unknown_structure`: Happens when a specified structure doesn't exist
- `unknown_criterion`: Happens when a specified scoreboard criterion doesn't exist
- `file_read_exception`: This is the parent for any exception that occurs due to an error while reading a file, including appdata load
- `nbt_read_exception`: Happens when there is an exception while reading an NBT file
- `json_read_exception`: Happens when there is an exception while reading a JSON file
- `user_defined_exception`: This is the parent for any exception thrown by the below `throw` function, except for those that explicitly declare
their parent to be one of the above
### `throw(id, message?, parent?)` This is the hierarchy of the exceptions that could be thrown/caught in the with the `try` function:
- `exception`: This is the base exception. Catching `'exception'` allows to catch everything that can be caught,
but like everywhere else, doing that sounds like a bad idea.
- `value_exception`: This is the parent for any exception that occurs due to an
incorrect argument value provided to a built-in function
- `unknown_item`, `unknown_block`, `unknown_biome`, `unknown_sound`, `unknown_particle`,
`unknown_poi_type`, `unknown_dimension`, `unknown_structure`, `unknown_criterion`: Specific
errors thrown when a specified internal name does not exist or is invalid.
- `io_exception`: This is the parent for any exception that occurs due to an error handling external data.
- `nbt_read_error`: Incorrect input NBT file.
- `json_read_error`: Incorrect input JSON data.
- `user_exception`: Exception thrown by default with `throw` function.
Throws an exception that can be caught in a `try` block (see above). If ran without arguments, it will pass `null` Synopsis:
as the value to the `catch_expr`. `message` is the message to show to chat or console in case the `throw` is <pre>
unhandled. inner_call() ->
(
aaa = 'booyah';
try(
for (range(10), item_tags('stick'+_*'k'));
,
print(_trace) // not caught, only catching user_exceptions
)
);
By default, the parent of any exception thrown by this function is `user_defined_exception`. outer_call() ->
You can specify the `parent` to be either an already existing exception from the list above, in which case your exception (
will not inherit from `user_defined_exception`, or specify a custom one, in which case your exception will inherit from try(
`exception` > `user_defined_exception` > `parent` > `id`. inner_call()
, 'exception', // catching everything
print(_trace)
)
);
</pre>
Producing:
```
{stack: [[<app>, inner_call, 1, 14]], locals: {_a: 0, aaa: booyah, _: 1, _y: 0, _i: 1, _x: 0, _z: 0}, token: [item_tags, 5, 23]}
```
If you don't want your exception to be handled, you can use `error` as the `parent` to throw an error which cannot be handled. ### `throw(value?)` `throw(type, value)`
Deprecated usage: `throw()` (without id): Specify at least the id of your exception Throws an exception that can be caught in a `try` block (see above). If ran without arguments, it will throw `user_exception`
passing `null` as the value to the `catch_expr`. With two arguments you can mimic any other exception type thrown in scarpet.
### `if(cond, expr, cond?, expr?, ..., default?)` ### `if(cond, expr, cond?, expr?, ..., default?)`
@@ -406,6 +406,7 @@ public class Tokenizer implements Iterator<Tokenizer.Token>
public int pos; public int pos;
public int linepos; public int linepos;
public int lineno; public int lineno;
public static final Token NONE = new Token();
public Token morphedInto(TokenType newType, String newSurface) public Token morphedInto(TokenType newType, String newSurface)
{ {
Token created = new Token(); Token created = new Token();
@@ -14,19 +14,21 @@ import java.util.function.Supplier;
public class ExpressionException extends RuntimeException implements ResolvedException public class ExpressionException extends RuntimeException implements ResolvedException
{ {
public final Context context; public final Context context;
public final Tokenizer.Token token;
public final List<FunctionValue> stack = new ArrayList<>(); public final List<FunctionValue> stack = new ArrayList<>();
private final Supplier<String> lazyStacktrace; private final Supplier<String> lazyStacktrace;
private String cachedMessage = null; private String cachedMessage = null;
public ExpressionException(Context c, Expression e, String message) public ExpressionException(Context c, Expression e, String message)
{ {
this(c, e, null, message); this(c, e, Tokenizer.Token.NONE, message);
} }
public ExpressionException(Context c, Expression e, Tokenizer.Token t, String message) public ExpressionException(Context c, Expression e, Tokenizer.Token t, String message)
{ {
super("Error"); super("Error");
lazyStacktrace = () -> makeMessage(c, e, t, message); lazyStacktrace = () -> makeMessage(c, e, t, message);
token = t;
context = c; context = c;
} }
public ExpressionException(Context c, Expression e, Tokenizer.Token t, String message, List<FunctionValue> stack) public ExpressionException(Context c, Expression e, Tokenizer.Token t, String message, List<FunctionValue> stack)
@@ -34,6 +36,7 @@ public class ExpressionException extends RuntimeException implements ResolvedExc
super("Error"); super("Error");
this.stack.addAll(stack); this.stack.addAll(stack);
lazyStacktrace = () -> makeMessage(c, e, t, message); lazyStacktrace = () -> makeMessage(c, e, t, message);
token = t;
context = c; context = c;
} }
@@ -41,7 +44,8 @@ public class ExpressionException extends RuntimeException implements ResolvedExc
{ {
super("Error"); super("Error");
this.stack.addAll(stack); this.stack.addAll(stack);
lazyStacktrace = messageSupplier; lazyStacktrace = () -> makeMessage(c, e, t, messageSupplier.get());
token = t;
context = c; context = c;
} }
@@ -13,7 +13,7 @@ public class Throwables {
private final String id; private final String id;
private final Throwables parent; private final Throwables parent;
private static final Map<String, Throwables> byValue = new HashMap<>(); private static final Map<String, Throwables> byId = new HashMap<>();
private static final Map<String, Set<Throwables>> handles = new HashMap<>(); private static final Map<String, Set<Throwables>> handles = new HashMap<>();
public static final Throwables THROWN_EXCEPTION_TYPE = register("exception", null); public static final Throwables THROWN_EXCEPTION_TYPE = register("exception", null);
@@ -27,10 +27,10 @@ public class Throwables {
public static final Throwables UNKNOWN_DIMENSION = register("unknown_dimension", VALUE_EXCEPTION); public static final Throwables UNKNOWN_DIMENSION = register("unknown_dimension", VALUE_EXCEPTION);
public static final Throwables UNKNOWN_STRUCTURE = register("unknown_structure", VALUE_EXCEPTION); public static final Throwables UNKNOWN_STRUCTURE = register("unknown_structure", VALUE_EXCEPTION);
public static final Throwables UNKNOWN_CRITERION = register("unknown_criterion", VALUE_EXCEPTION); public static final Throwables UNKNOWN_CRITERION = register("unknown_criterion", VALUE_EXCEPTION);
public static final Throwables FILE_READ_EXCEPTION = register("file_read_exception", THROWN_EXCEPTION_TYPE); public static final Throwables IO_EXCEPTION = register("io_exception", THROWN_EXCEPTION_TYPE);
public static final Throwables NBT_READ = register("nbt_read_exception", FILE_READ_EXCEPTION); public static final Throwables NBT_READ = register("nbt_read_error", IO_EXCEPTION);
public static final Throwables JSON_READ = register("json_read_exception", FILE_READ_EXCEPTION); public static final Throwables JSON_READ = register("json_read_error", IO_EXCEPTION);
public static final Throwables USER_DEFINED = register("user_defined_exception", THROWN_EXCEPTION_TYPE); public static final Throwables USER_DEFINED = register("user_exception", THROWN_EXCEPTION_TYPE);
/** /**
* Creates an exception and registers it to be used as parent for * Creates an exception and registers it to be used as parent for
@@ -43,7 +43,7 @@ public class Throwables {
public static Throwables register(String id, Throwables parent) public static Throwables register(String id, Throwables parent)
{ {
Throwables exc = new Throwables(id, parent); Throwables exc = new Throwables(id, parent);
byValue.put(id, exc); byId.put(id, exc);
Set<Throwables> itHandles = new HashSet<>(); Set<Throwables> itHandles = new HashSet<>();
itHandles.add(exc); itHandles.add(exc);
while (parent!=null) while (parent!=null)
@@ -69,7 +69,7 @@ public class Throwables {
public static Throwables getTypeForException(String type) public static Throwables getTypeForException(String type)
{ {
Throwables properType = byValue.get(type); Throwables properType = byId.get(type);
if (properType == null) throw new InternalExpressionException("Unknown exception type: "+type); if (properType == null) throw new InternalExpressionException("Unknown exception type: "+type);
return properType; return properType;
} }
@@ -8,7 +8,14 @@ import carpet.script.exception.InternalExpressionException;
import carpet.script.exception.ProcessedThrowStatement; import carpet.script.exception.ProcessedThrowStatement;
import carpet.script.exception.ThrowStatement; import carpet.script.exception.ThrowStatement;
import carpet.script.exception.Throwables; import carpet.script.exception.Throwables;
import carpet.script.value.ListValue;
import carpet.script.value.MapValue;
import carpet.script.value.NumericValue;
import carpet.script.value.StringValue;
import carpet.script.value.Value; import carpet.script.value.Value;
import com.google.common.collect.ImmutableMap;
import java.util.stream.Collectors;
public class ControlFlow { public class ControlFlow {
public static void apply(Expression expression) // public just to get the javadoc right public static void apply(Expression expression) // public just to get the javadoc right
@@ -85,6 +92,25 @@ public class ControlFlow {
LazyValue __ = c.getVariable("_"); LazyValue __ = c.getVariable("_");
c.setVariable("_", (__c, __t) -> ret.data.reboundedTo("_")); c.setVariable("_", (__c, __t) -> ret.data.reboundedTo("_"));
LazyValue _trace = c.getVariable("_trace");
c.setVariable("_trace", (__c, __t) -> MapValue.wrap(ImmutableMap.of(
StringValue.of("stack"), ListValue.wrap(ret.stack.stream().map(f -> ListValue.of(
StringValue.of(f.getModule().getName()),
StringValue.of(f.getString()),
NumericValue.of(f.getToken().lineno+1),
NumericValue.of(f.getToken().linepos+1)
)).collect(Collectors.toList())),
StringValue.of("locals"), MapValue.wrap(ret.context.variables.entrySet().stream().collect(Collectors.toMap(
e -> StringValue.of(e.getKey()),
e -> e.getValue().evalValue(ret.context)
))),
StringValue.of("token"), ListValue.of(
StringValue.of(ret.token.surface),
NumericValue.of(ret.token.lineno+1),
NumericValue.of(ret.token.linepos+1)
)
)));
if (lv.size() == 2) if (lv.size() == 2)
{ {
@@ -105,7 +131,10 @@ public class ControlFlow {
} }
} }
c.setVariable("_", __); c.setVariable("_", __);
if (_trace != null)
c.setVariable("_trace", _trace);
else
c.delVariable("_trace");
if (val == null) // not handled if (val == null) // not handled
{ {
throw ret; throw ret;