diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index 64b07dfd..1a7d3c33 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -11,6 +11,8 @@ optional `mixer`. Default values for `volume`, `pitch` and `mixer` are `1.0`, `1 Valid mixer options are `master`, `music`, `record`, `weather`, `block`, `hostile`,`neutral`, `player`, `ambient` and `voice`. `pos` can be either a block, triple of coords, or a list of thee numbers. Uses the same options as a corresponding `playsound` command. + +Throws `unknown_sound` if sound doesn't exist. ## Particles @@ -21,12 +23,15 @@ of 0, and to all players nearby, but these options can be changed via optional a command on details on those options. Valid particle names are for example `'angry_villager', 'item diamond', 'block stone', 'dust 0.8 0.1 0.1 4'`. +Throws `unknown_particle` if particle doesn't exist. + ### `particle_line(name, pos, pos2, density?, player?)` Renders a line of particles from point `pos` to `pos2` with supplied density (defaults to 1), which indicates how far apart you would want particles to appear, so `0.1` means one every 10cm. If a player (or player name) is supplied, only that player will receive particles. +Throws `unknown_particle` if particle doesn't exist. ### `particle_box(name, pos, pos2, density?, player?)` ### `particle_rect` (deprecated) @@ -34,6 +39,8 @@ that player will receive particles. Renders a cuboid of particles between points `pos` and `pos2` with supplied density. If a player (or player name) is supplied, only that player will receive particles. +Throws `unknown_particle` if particle doesn't exist. + ## Markers ### `draw_shape(shape, duration, key?, value?, ... )`, @@ -320,6 +327,10 @@ record to the file. Since files are closed after each write, sending multiple li write is beneficial for writing speed. To send multiple packs of data, either provide them flat or as a list in the third argument. +Throws: +- `nbt_read_error`: When failed to read NBT file. +- `json_read_error`: When failed to read JSON file. The exception data will contain details about the problem. +
write_file('foo', 'shared_text, ['one', 'two']);
write_file('foo', 'shared_text', 'three\n', 'four\n');
@@ -371,6 +382,8 @@ specific data directory is under `world/scripts/foo.data/bar/../baz.nbt`, and sh
You can use app data to save non-vanilla information separately from the world and other scripts.
+Throws `nbt_read_error` if failed to read app data.
+
### `store_app_data(tag)`
Note: `store_app_data(tag, file)` and `store_app_data(tag, file, shared?)` usages deprecated. Use `write_file` instead.
@@ -525,6 +538,8 @@ Evaluates the expression `expr` with different dimension execution context. `smt
world-localized block, so not `block('stone')`, or a string representing a dimension like:
`'nether'`, `'the_nether'`, `'end'` or `'overworld'`, etc.
+Throws `unknown_dimension` if provided dimension can't be found.
+
### `view_distance()`
Returns the view distance of the server.
diff --git a/docs/scarpet/api/BlocksAndWorldAccess.md b/docs/scarpet/api/BlocksAndWorldAccess.md
index bb791fe0..540bc9d8 100644
--- a/docs/scarpet/api/BlocksAndWorldAccess.md
+++ b/docs/scarpet/api/BlocksAndWorldAccess.md
@@ -21,6 +21,8 @@ when it was used. Block values passed in various places like `scan` functions, e
its properties are needed. This means that if the block at the location changes before its queried in the program this
might result in getting the later state, which might not be desired. Consider the following example:
+Throws `unknown_block` if provided input is not valid.
+
set(10,10,10,'stone');
scan(10,10,10,0,0,0, b = _);
set(10,10,10,'air');
@@ -50,6 +52,8 @@ properties that the original source block may have contained.
The returned value is either the block state that has been set, or `false` if block setting was skipped, or failed
+Throws `unknown_block` if provided block to set is not valid
+
set(0,5,0,'bedrock') => bedrock
set(l(0,5,0), 'bedrock') => bedrock
@@ -128,6 +132,8 @@ but doesn't open chests / containers, so have no effect on interactive blocks, l
Returns true if placement/use was
successful, false otherwise.
+Throws `unknown_item` if `item` doesn't exist
+
place_item('stone',x,y,z) // places a stone block on x,y,z block
place_item('piston,x,y,z,'down') // places a piston facing down
@@ -146,6 +152,8 @@ If type is `null`, POI at position is removed. In any case, previous POI is also
Interestingly, `unemployed`, and `nitwit` are not used in the game, meaning, they could be used as permanent spatial
markers for scarpet apps. `meeting` is the only one with increased max occupancy of 32.
+Throws `unknown_poi` if the provided point of interest doesn't exist
+
### `set_biome(pos, biome_name, update=true)`
Changes the biome at that block position. if update is specified and false, then chunk will not be refreshed
@@ -156,6 +164,8 @@ is only effective if you set it at y=0, and affects the entire column of. In the
specific Y coordinate of the biome you want to change, and it affects roughly 4x4x4 area (give or take some random
noise).
+Throws `unknown_biome` if the `biome_name` doesn't exist.
+
### `update(pos)`
Causes a block update at position.
@@ -180,6 +190,8 @@ In item context, `true` means that breaking item has no nbt to use, `null` indic
considered broken in process, and `nbt` type value, for a resulting NBT tag on a hypothetical tool. Its up to the
programmer to use that nbt to apply it where it belong
+Throws `unknown_item` if `tool` doesn't exist.
+
Here is a sample code that can be used to mine blocks using items in player inventory, without using player context
for mining. Obviously, in this case the use of `harvest` would be much more applicable:
@@ -271,6 +283,8 @@ back in state definition in various applications where block properties are requ
`block_state` can also accept block names as input, returning block's default state.
+Throws `unknown_block` if the provided input is not valid.
+
set(x,y,z,'iron_trapdoor','half','top'); block_state(x,y,z) => {waterlogged: false, half: top, open: false, ...}
set(x,y,z,'iron_trapdoor','half','top'); block_state(x,y,z,'half') => top
@@ -291,6 +305,8 @@ Without arguments, returns list of available tags, with block supplied (either b
of tags the block belongs to, and if a tag is specified, returns `null` if tag is invalid, `false` if this block doesn't belong
to this tag, and `true` if the block belongs to the tag.
+Throws `unknown_block` if `block` doesn't exist
+
### `block_data(pos)`
Return NBT string associated with specific location, or null if the block does not carry block data. Can be currently
@@ -325,9 +341,9 @@ poi(x,y,z,5) => [['nether_portal',0,[7,8,9]],['nether_portal',0,[7,9,9]]] // two
Without arguments, returns the list of biomes in the world.
-With block, or name, returns the name of the biome in that position, or null, if provided biome is not valid.
+With block, or name, returns the name of the biome in that position, or throws `'unknown_biome'` if provided biome or block are not valid.
-With an optional feature, it returns value for the specified attribute for that biome. Available and querable features include:
+With an optional feature, it returns value for the specified attribute for that biome. Available and queryable features include:
* `'top_material'`: unlocalized block representing the top surface material
* `'under_material'`: unlocalized block representing what sits below topsoil
* `'category'`: the parent biome this biome is derived from. Possible values include:
@@ -724,6 +740,7 @@ If structure is not specified, it will return a set of structure names that are
as keys, and same type of map values as with a single structure call. An empty set or an empty map would indicate that nothing
should be generated there.
+Throws `unknown_structure` if structure doesn't exist.
### `structures(pos), structures(pos, structure_name)`
@@ -757,6 +774,8 @@ structure starts.
Requires a `Structure Variant` or `Standard Structure` name (see above). If standard name is used, the variant of the
structure may depend on the biome, otherwise the default structure for this type will be generated.
+Throws `unknown_structure` if structure doesn't exist.
+
### `plop(pos, what)`
Plops a structure or a feature at a given `pos`, so block, triple position coordinates or a list of coordinates.
diff --git a/docs/scarpet/api/Inventories.md b/docs/scarpet/api/Inventories.md
index ebe2ed7b..b77c26e6 100644
--- a/docs/scarpet/api/Inventories.md
+++ b/docs/scarpet/api/Inventories.md
@@ -28,14 +28,16 @@ an inventory, all API functions typically do nothing and return null.
Most items returned are in the form of a triple of item name, count, and nbt or the extra data associated with an item.
-### item_list(tag?)
+### `item_list(tag?)`
With no arguments, returns a list of all items in the game. With an item tag provided, list items matching the tag, or `null` if tag is not valid.
-### item_tags(item, tag?)
+### `item_tags(item, tag?)`
Returns list of tags the item belongs to, or, if tag is provided, `true` if an item maches the tag, `false` if it doesn't and `null` if that's not a valid tag
+Throws `unknown_item` if item doesn't exist.
+
### `stack_limit(item)`
Returns number indicating what is the stack limit for the item. Its typically 1 (non-stackable), 16 (like buckets),
@@ -43,6 +45,8 @@ or 64 - rest. It is recommended to consult this, as other inventory API function
it is up to the programmer to keep it at bay. As of 1.13, game checks for negative numbers and setting an item to
negative is the same as empty.
+Throws `unknown_item` if item doesn't exist.
+
stack_limit('wooden_axe') => 1
stack_limit('ender_pearl') => 16
@@ -53,6 +57,8 @@ stack_limit('stone') => 64
Returns the string representing the category of a given item, like `building_blocks`, `combat`, or `tools`.
+Throws `unknown_item` if item doesn't exist.
+
item_category('wooden_axe') => tools
item_category('ender_pearl') => misc
@@ -175,6 +181,8 @@ while( (slot = inventory_find(p, 'diamond', slot)) != null, 41, drop_item(p, slo
inventory_drop(x,y,z, 0) => 64 // removed and spawned in the world a full stack of items
+Throws `unknown_item` if item doesn't exist.
+
### `inventory_remove(inventory, item, amount?)`
Removes amount (defaults to 1) of item from inventory. If the inventory doesn't have the defined amount, nothing
diff --git a/docs/scarpet/api/Scoreboard.md b/docs/scarpet/api/Scoreboard.md
index 826c5328..9d152098 100644
--- a/docs/scarpet/api/Scoreboard.md
+++ b/docs/scarpet/api/Scoreboard.md
@@ -14,6 +14,8 @@ Adds a new objective to scoreboard. If `criterion` is not specified, assumes `'d
If the objective already exists, changes the criterion of that objective and returns `false`. If the criterion was not specified but the objective already exists, returns the current criterion.
If the objective was added, returns `true`. If nothing is affected, returns `null`
+Throws `unknown_criterion` if criterion doesn't exist.
+
scoreboard_add('counter')
scoreboard_add('lvl','level')
diff --git a/docs/scarpet/language/FunctionsAndControlFlow.md b/docs/scarpet/language/FunctionsAndControlFlow.md
index 3d11e8bf..a02c2d51 100644
--- a/docs/scarpet/language/FunctionsAndControlFlow.md
+++ b/docs/scarpet/language/FunctionsAndControlFlow.md
@@ -234,15 +234,73 @@ 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.
-### `try(expr, catch_expr(_)?) ... throw(value?)`
+### `try(expr)` `try(expr, user_catch_expr)` `try(expr, type, catch_expr, type?, catch_expr?, ...)`
-`try` function evaluates expression, and continues further unless `throw` function is called anywhere
-inside `expr`. In that case the `catch_expr` is evaluates with `_` set to the argument `throw` was called with.
-This mechanic accepts skipping thrown value - it throws null instead, and catch expression - then try returns
-null as well This mechanism allows to terminate large portion of a convoluted call stack and continue program
-execution. There is only one level of exceptions currently in carpet, so if the inner function also defines
-the `try` catchment area, it will received the exception first, but it can technically rethrow the value its
-getting for the outer scope. Unhandled throw acts like an exit statement.
+`try` evaluates expression, allowing capturing exceptions that would be thrown inside `expr` statement. The exceptions can be
+thrown explicitly using `throw()` or internally by scarpet where code is correct but detects illegal state. The 2-argument form
+catches only user-thrown exceptions and one argument call `try(expr)` is equivalent to `try(expr, null)`,
+or `try(expr, 'user_exception', null)`. If multiple `type-catch` pairs are defined, the execution terminates on the first
+applicable type for the exception thrown. Therefore, even if the caught exception matches multiple filters, only
+the first matching block will be executed.
+
+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.
+
+
+You can use `try` mechanism to exit from large portion of a convoluted call stack and continue program execution, although catching
+exceptions is typically much more expensive comparing to not throwing them.
+
+The `try` function allows you to catch some scarpet exceptions for cases covering invalid data, like invalid
+blocks, biomes, dimensions and other things, that may have been modified by datapacks, resourcepacks or other mods,
+or when an error is outside of the programmers scope, such as problems when reading or decoding files.
+
+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.
+
+Synopsis:
+
+inner_call() ->
+(
+ aaa = 'booyah';
+ try(
+ for (range(10), item_tags('stick'+_*'k'));
+ ,
+ print(_trace) // not caught, only catching user_exceptions
+ )
+);
+
+outer_call() ->
+(
+ try(
+ inner_call()
+ , 'exception', // catching everything
+ print(_trace)
+ )
+);
+
+Producing:
+```
+{stack: [[, inner_call, 1, 14]], locals: {_a: 0, aaa: booyah, _: 1, _y: 0, _i: 1, _x: 0, _z: 0}, token: [item_tags, 5, 23]}
+```
+
+### `throw(value?)`, `throw(type, value)`, `throw(subtype, type, value)`
+
+Throws an exception that can be caught in a `try` block (see above). If ran without arguments, it will throw a `user_exception`
+passing `null` as the value to the `catch_expr`. With two arguments you can mimic any other exception type thrown in scarpet.
+With 3 arguments, you can specify a custom exception acting as a `subtype` of a provided `type`, allowing to customize `try`
+statements with custom exceptions.
### `if(cond, expr, cond?, expr?, ..., default?)`
diff --git a/docs/scarpet/resources/editors/idea/3.txt b/docs/scarpet/resources/editors/idea/3.txt
index 333d6259..fc9e16f0 100644
--- a/docs/scarpet/resources/editors/idea/3.txt
+++ b/docs/scarpet/resources/editors/idea/3.txt
@@ -4,6 +4,7 @@ _i
_x
_y
_z
+_trace
euler
false
null
diff --git a/docs/scarpet/resources/editors/npp/scarpet.xml b/docs/scarpet/resources/editors/npp/scarpet.xml
index 5ae29a0f..b44bb643 100644
--- a/docs/scarpet/resources/editors/npp/scarpet.xml
+++ b/docs/scarpet/resources/editors/npp/scarpet.xml
@@ -91,7 +91,7 @@
euler pi null true false
x y z p
- _ _i _a _x _y _z
+ _ _i _a _x _y _z _trace
global_
__command
diff --git a/src/main/java/carpet/CarpetSettings.java b/src/main/java/carpet/CarpetSettings.java
index 201b8db2..99bb6af6 100644
--- a/src/main/java/carpet/CarpetSettings.java
+++ b/src/main/java/carpet/CarpetSettings.java
@@ -417,20 +417,26 @@ public class CarpetSettings
private static class ModulePermissionLevel extends Validator {
@Override public String validate(ServerCommandSource source, ParsedRule currentRule, String newValue, String string) {
- CarpetSettings.runPermissionLevel = SettingsManager.getCommandLevel(newValue);
+ int permissionLevel = SettingsManager.getCommandLevel(newValue);
+ if (source != null && !source.hasPermissionLevel(permissionLevel))
+ return null;
+ CarpetSettings.runPermissionLevel = permissionLevel;
+ CarpetServer.settingsManager.notifyPlayersCommandsChanged();
return newValue;
}
@Override
- public String description() { return "Also controls permission level of commands executed via `run()`";}
+ public String description() { return "When changing the rule, you must at least have the permission level you are trying to give it";}
}
@Rule(
desc = "Enables restrictions for arbitrary code execution with scarpet",
extra = {
"Users that don't have this permission level",
- "won't be able to load apps or /script run"
+ "won't be able to load apps or /script run.",
+ "It is also the permission level apps will",
+ "have when running commands with run()"
},
- category = {COMMAND, SCARPET},
- validate = ModulePermissionLevel.class
+ category = {SCARPET},
+ validate = {Validator._COMMAND_LEVEL_VALIDATOR.class, ModulePermissionLevel.class}
)
public static String commandScriptACE = "ops";
diff --git a/src/main/java/carpet/script/CarpetScriptServer.java b/src/main/java/carpet/script/CarpetScriptServer.java
index 8634ca00..fc9eb5e2 100644
--- a/src/main/java/carpet/script/CarpetScriptServer.java
+++ b/src/main/java/carpet/script/CarpetScriptServer.java
@@ -26,6 +26,7 @@ import net.minecraft.util.WorldSavePath;
import net.minecraft.util.math.BlockPos;
import java.io.IOException;
+import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -136,7 +137,8 @@ public class CarpetScriptServer
{
try {
Path folder = server.getSavePath(WorldSavePath.ROOT).resolve("scripts");
- Files.createDirectories(folder);
+ if (!Files.exists(folder))
+ Files.createDirectories(folder);
Optional
scriptPath = Files.list(folder)
.filter(script ->
@@ -149,7 +151,8 @@ public class CarpetScriptServer
if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT)
{
Path globalFolder = FabricLoader.getInstance().getConfigDir().resolve("carpet/scripts");
- Files.createDirectories(globalFolder);
+ if (!Files.exists(globalFolder))
+ Files.createDirectories(globalFolder);
scriptPath = Files.walk(globalFolder)
.filter(script -> script.getFileName().toString().equalsIgnoreCase(name + ".sc") ||
(allowLibraries && script.getFileName().toString().equalsIgnoreCase(name + ".scl")))
@@ -194,7 +197,8 @@ public class CarpetScriptServer
}
try {
Path worldScripts = server.getSavePath(WorldSavePath.ROOT).resolve("scripts");
- Files.createDirectories(worldScripts);
+ if (!Files.exists(worldScripts))
+ Files.createDirectories(worldScripts);
Files.list(worldScripts)
.filter(f -> f.toString().endsWith(".sc"))
.forEach(f -> moduleNames.add(f.getFileName().toString().replaceFirst("\\.sc$","").toLowerCase(Locale.ROOT)));
@@ -202,8 +206,9 @@ public class CarpetScriptServer
if (includeBuiltIns && (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT))
{
Path globalScripts = FabricLoader.getInstance().getConfigDir().resolve("carpet/scripts");
- Files.createDirectories(globalScripts);
- Files.walk(globalScripts)
+ if (!Files.exists(globalScripts))
+ Files.createDirectories(globalScripts);
+ Files.walk(globalScripts, FileVisitOption.FOLLOW_LINKS)
.filter(f -> f.toString().endsWith(".sc"))
.forEach(f -> moduleNames.add(f.getFileName().toString().replaceFirst("\\.sc$","").toLowerCase(Locale.ROOT)));
}
diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java
index ad9eb7f5..cb95e860 100644
--- a/src/main/java/carpet/script/Expression.java
+++ b/src/main/java/carpet/script/Expression.java
@@ -19,7 +19,6 @@ import carpet.script.exception.ExpressionException;
import carpet.script.exception.InternalExpressionException;
import carpet.script.exception.ResolvedException;
import carpet.script.exception.ReturnStatement;
-import carpet.script.exception.ThrowStatement;
import carpet.script.language.Arithmetic;
import carpet.script.language.ControlFlow;
import carpet.script.language.DataStructures;
@@ -232,7 +231,7 @@ public class Expression
if (exc instanceof ExitStatement)
return exc;
if (exc instanceof InternalExpressionException)
- return new ExpressionException(c, e, token, exc.getMessage(), ((InternalExpressionException) exc).stack);
+ return ((InternalExpressionException) exc).promote(c, e, token);
if (exc instanceof ArithmeticException)
return new ExpressionException(c, e, token, "Your math is wrong, "+exc.getMessage());
if (exc instanceof ResolvedException)
@@ -624,9 +623,9 @@ public class Expression
{
return exprProvider.get().evalValue(c, expectedType);
}
- catch (ContinueStatement | BreakStatement | ReturnStatement | ThrowStatement exc)
+ catch (ContinueStatement | BreakStatement | ReturnStatement exc)
{
- throw new ExpressionException(c, this, "Control flow functions, like continue, break, throw or return, should only be used in loops, try blocks, and functions respectively.");
+ throw new ExpressionException(c, this, "Control flow functions, like continue, break or return, should only be used in loops, and functions respectively.");
}
catch (ExitStatement exit)
{
diff --git a/src/main/java/carpet/script/Tokenizer.java b/src/main/java/carpet/script/Tokenizer.java
index 30fac871..24a92df1 100644
--- a/src/main/java/carpet/script/Tokenizer.java
+++ b/src/main/java/carpet/script/Tokenizer.java
@@ -406,6 +406,7 @@ public class Tokenizer implements Iterator
public int pos;
public int linepos;
public int lineno;
+ public static final Token NONE = new Token();
public Token morphedInto(TokenType newType, String newSurface)
{
Token created = new Token();
diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java
index 653c80e4..3f79856b 100644
--- a/src/main/java/carpet/script/api/Auxiliary.java
+++ b/src/main/java/carpet/script/api/Auxiliary.java
@@ -19,6 +19,8 @@ import carpet.script.argument.FunctionArgument;
import carpet.script.argument.Vector3Argument;
import carpet.script.exception.ExitStatement;
import carpet.script.exception.InternalExpressionException;
+import carpet.script.exception.ThrowStatement;
+import carpet.script.exception.Throwables;
import carpet.script.utils.FixedCommandSource;
import carpet.script.utils.InputValidator;
import carpet.script.utils.ScarpetJsonDeserializer;
@@ -79,6 +81,7 @@ import net.minecraft.text.LiteralText;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.InvalidIdentifierException;
+import net.minecraft.util.crash.CrashException;
import net.minecraft.util.Util;
import net.minecraft.util.WorldSavePath;
import net.minecraft.util.dynamic.RegistryOps;
@@ -166,10 +169,11 @@ public class Auxiliary {
{
expression.addLazyFunction("sound", -1, (c, t, lv) -> {
CarpetContext cc = (CarpetContext)c;
- Identifier soundName = new Identifier(lv.get(0).evalValue(c).getString());
+ String rawString = lv.get(0).evalValue(c).getString();
+ Identifier soundName = new Identifier(rawString);
Vector3Argument locator = Vector3Argument.findIn(cc, lv, 1);
if (Registry.SOUND_EVENT.get(soundName) == null)
- throw new InternalExpressionException("No such sound: "+soundName.getPath());
+ throw new ThrowStatement(rawString, Throwables.UNKNOWN_SOUND);
float volume = 1.0F;
float pitch = 1.0F;
SoundCategory mixer = SoundCategory.MASTER;
@@ -876,7 +880,15 @@ public class Auxiliary {
Value retVal;
if (fdesc.getMiddle().equals("nbt"))
{
- Tag state = ((CarpetScriptHost) c.host).readFileTag(fdesc.getLeft(), fdesc.getRight());
+ Tag state;
+ try
+ {
+ state = ((CarpetScriptHost) c.host).readFileTag(fdesc.getLeft(), fdesc.getRight());
+ } catch (CrashException e)
+ {
+ throw new ThrowStatement((fdesc.getRight()?"shared/":"")+ fdesc.getLeft(), Throwables.NBT_READ);
+ }
+
if (state == null) return LazyValue.NULL;
retVal = new NBTSerializableValue(state);
}
@@ -889,10 +901,13 @@ public class Auxiliary {
}
catch (JsonParseException e)
{
- Throwable exception = e;
+ Throwable exc = e;
if(e.getCause() != null)
- exception = e.getCause();
- throw new InternalExpressionException("Failed to read JSON file: "+exception.getMessage());
+ exc = e.getCause();
+ throw new ThrowStatement(MapValue.wrap(ImmutableMap.of(
+ StringValue.of("error"), StringValue.of(exc.getMessage()),
+ StringValue.of("path"), StringValue.of((fdesc.getRight()?"shared/":"")+ fdesc.getLeft())
+ )), Throwables.JSON_READ);
}
Value parsedJson = GSON.fromJson(json, Value.class);
if (parsedJson == null)
@@ -980,7 +995,14 @@ public class Auxiliary {
shared = lv.get(1).evalValue(c).getBoolean();
}
}
- Tag state = ((CarpetScriptHost)((CarpetContext)c).host).readFileTag(file, shared);
+ Tag state;
+ try
+ {
+ state = ((CarpetScriptHost)((CarpetContext)c).host).readFileTag(file, shared);
+ } catch (CrashException e)
+ {
+ throw new ThrowStatement("app data", Throwables.NBT_READ);
+ }
if (state == null)
return (cc, tt) -> Value.NULL;
Value retVal = new NBTSerializableValue(state);
diff --git a/src/main/java/carpet/script/api/Inventories.java b/src/main/java/carpet/script/api/Inventories.java
index 313ea9f9..96234cd2 100644
--- a/src/main/java/carpet/script/api/Inventories.java
+++ b/src/main/java/carpet/script/api/Inventories.java
@@ -6,6 +6,8 @@ import carpet.script.CarpetContext;
import carpet.script.Expression;
import carpet.script.LazyValue;
import carpet.script.exception.InternalExpressionException;
+import carpet.script.exception.ThrowStatement;
+import carpet.script.exception.Throwables;
import carpet.script.value.ListValue;
import carpet.script.value.NBTSerializableValue;
import carpet.script.value.NullValue;
@@ -119,14 +121,7 @@ public class Inventories {
}
}
List> recipes;
- try
- {
- recipes = ((RecipeManagerInterface) cc.s.getMinecraftServer().getRecipeManager()).getAllMatching(type, new Identifier(recipeName));
- }
- catch (InvalidIdentifierException ignored)
- {
- return LazyValue.NULL;
- }
+ recipes = ((RecipeManagerInterface) cc.s.getMinecraftServer().getRecipeManager()).getAllMatching(type, new Identifier(recipeName));
if (recipes.isEmpty())
return LazyValue.NULL;
List recipesOutput = new ArrayList<>();
@@ -197,17 +192,9 @@ public class Inventories {
{
String itemStr = lv.get(0).evalValue(c).getString();
Item item;
- try
- {
- Identifier id = new Identifier(itemStr);
- item = Registry.ITEM.get(id);
- if (item == Items.AIR && !id.getPath().equalsIgnoreCase("air"))
- throw new InvalidIdentifierException("boo");
- }
- catch (InvalidIdentifierException ignored)
- {
- throw new InternalExpressionException("Incorrect item: "+itemStr);
- }
+ Identifier id = new Identifier(itemStr);
+ item = Registry.ITEM.getOrEmpty(id).orElseThrow(() -> new ThrowStatement(itemStr, Throwables.UNKNOWN_ITEM));
+
if (!item.hasRecipeRemainder()) return LazyValue.NULL;
Value ret = new StringValue(NBTSerializableValue.nameFromRegistryId(Registry.ITEM.getId(item.getRecipeRemainder())));
return (_c, _t ) -> ret;
diff --git a/src/main/java/carpet/script/api/Scoreboards.java b/src/main/java/carpet/script/api/Scoreboards.java
index ac115283..0ec99dc0 100644
--- a/src/main/java/carpet/script/api/Scoreboards.java
+++ b/src/main/java/carpet/script/api/Scoreboards.java
@@ -6,6 +6,8 @@ import carpet.script.CarpetContext;
import carpet.script.Expression;
import carpet.script.LazyValue;
import carpet.script.exception.InternalExpressionException;
+import carpet.script.exception.ThrowStatement;
+import carpet.script.exception.Throwables;
import carpet.script.value.EntityValue;
import carpet.script.value.ListValue;
import carpet.script.value.NullValue;
@@ -131,7 +133,7 @@ public class Scoreboards {
criterion = ScoreboardCriterion.getOrCreateStatCriterion(critetionName).orElse(null);
if (criterion==null)
{
- throw new InternalExpressionException("Unknown scoreboard criterion: "+critetionName);
+ throw new ThrowStatement(critetionName, Throwables.UNKNOWN_CRITERION);
}
}
diff --git a/src/main/java/carpet/script/api/WorldAccess.java b/src/main/java/carpet/script/api/WorldAccess.java
index eeb9a080..85a0630b 100644
--- a/src/main/java/carpet/script/api/WorldAccess.java
+++ b/src/main/java/carpet/script/api/WorldAccess.java
@@ -19,6 +19,8 @@ import carpet.script.LazyValue;
import carpet.script.argument.BlockArgument;
import carpet.script.argument.Vector3Argument;
import carpet.script.exception.InternalExpressionException;
+import carpet.script.exception.ThrowStatement;
+import carpet.script.exception.Throwables;
import carpet.script.utils.BiomeInfo;
import carpet.script.utils.WorldTools;
import carpet.script.value.BlockValue;
@@ -59,7 +61,6 @@ import net.minecraft.item.SwordItem;
import net.minecraft.item.TridentItem;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
-import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ChunkTicket;
import net.minecraft.server.world.ChunkTicketType;
@@ -106,7 +107,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiFunction;
-import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -336,8 +336,8 @@ public class WorldAccess {
String poiType = lv.get(locator.offset+1).evalValue(c).getString().toLowerCase(Locale.ROOT);
if (!"any".equals(poiType))
{
- PointOfInterestType type = Registry.POINT_OF_INTEREST_TYPE.get(new Identifier(poiType));
- if (type == PointOfInterestType.UNEMPLOYED && !"unemployed".equals(poiType)) return LazyValue.NULL;
+ PointOfInterestType type = Registry.POINT_OF_INTEREST_TYPE.getOrEmpty(new Identifier(poiType))
+ .orElseThrow(() -> new ThrowStatement(poiType, Throwables.UNKNOWN_POI));
condition = (tt) -> tt == type;
}
if (locator.offset + 2 < lv.size())
@@ -391,9 +391,8 @@ public class WorldAccess {
return LazyValue.FALSE;
}
String poiTypeString = poi.getString().toLowerCase(Locale.ROOT);
- PointOfInterestType type = Registry.POINT_OF_INTEREST_TYPE.get(new Identifier(poiTypeString));
- // solving lack of null with defaulted registries
- if (type == PointOfInterestType.UNEMPLOYED && !"unemployed".equals(poiTypeString)) throw new InternalExpressionException("Unknown POI type: "+poiTypeString);
+ PointOfInterestType type = Registry.POINT_OF_INTEREST_TYPE.getOrEmpty(new Identifier(poiTypeString))
+ .orElseThrow(() -> new ThrowStatement(poiTypeString, Throwables.UNKNOWN_POI));
int occupancy = 0;
if (locator.offset + 1 < lv.size())
{
@@ -872,9 +871,8 @@ public class WorldAccess {
{
playerBreak = true;
String itemString = val.getString();
- item = Registry.ITEM.get(new Identifier(itemString));
- if (item == Items.AIR && !itemString.equals("air"))
- throw new InternalExpressionException("Incorrect item: " + itemString);
+ item = Registry.ITEM.getOrEmpty(new Identifier(itemString))
+ .orElseThrow(() -> new ThrowStatement(itemString, Throwables.UNKNOWN_ITEM));
}
}
CompoundTag tag = null;
@@ -1152,7 +1150,7 @@ public class WorldAccess {
if (locator.replacement != null)
{
biome = world.getRegistryManager().get(Registry.BIOME_KEY).get(new Identifier(locator.replacement));
- if (biome == null) return LazyValue.NULL;
+ if (biome == null) throw new ThrowStatement(locator.replacement, Throwables.UNKNOWN_BIOME) ;
}
else
{
@@ -1181,9 +1179,8 @@ public class WorldAccess {
throw new InternalExpressionException("'set_biome' needs a biome name as an argument");
String biomeName = lv.get(locator.offset+0).evalValue(c).getString();
// from locatebiome command code
- Biome biome = cc.s.getMinecraftServer().getRegistryManager().get(Registry.BIOME_KEY).get(new Identifier(biomeName));
- if (biome == null)
- throw new InternalExpressionException("Unknown biome: "+biomeName);
+ Biome biome = cc.s.getMinecraftServer().getRegistryManager().get(Registry.BIOME_KEY).getOrEmpty(new Identifier(biomeName))
+ .orElseThrow(() -> new ThrowStatement(biomeName, Throwables.UNKNOWN_BIOME));
boolean doImmediateUpdate = true;
if (lv.size() > locator.offset+1)
{
@@ -1252,8 +1249,8 @@ public class WorldAccess {
if (!(requested instanceof NullValue))
{
String reqString = requested.getString();
- structure = Registry.STRUCTURE_FEATURE.get(new Identifier(reqString));
- if (structure == null) throw new InternalExpressionException("Unknown structure: " + reqString);
+ structure = Registry.STRUCTURE_FEATURE.getOrEmpty(new Identifier(reqString))
+ .orElseThrow(() -> new ThrowStatement(reqString, Throwables.UNKNOWN_STRUCTURE));
}
if (lv.size() > locator.offset+1)
{
@@ -1333,7 +1330,7 @@ public class WorldAccess {
throw new InternalExpressionException("'set_structure requires at least position and a structure name");
String structureName = lv.get(locator.offset).evalValue(c).getString().toLowerCase(Locale.ROOT);
ConfiguredStructureFeature, ?> configuredStructure = FeatureGenerator.resolveConfiguredStructure(structureName, world, pos);
- if (configuredStructure == null) throw new InternalExpressionException("Unknown structure: "+structureName);
+ if (configuredStructure == null) throw new ThrowStatement(structureName, Throwables.UNKNOWN_STRUCTURE);
// good 'ol pointer
Value[] result = new Value[]{Value.NULL};
// technically a world modification. Even if we could let it slide, we will still park it
diff --git a/src/main/java/carpet/script/exception/ExpressionException.java b/src/main/java/carpet/script/exception/ExpressionException.java
index 34d34c96..419fb345 100644
--- a/src/main/java/carpet/script/exception/ExpressionException.java
+++ b/src/main/java/carpet/script/exception/ExpressionException.java
@@ -8,28 +8,44 @@ import carpet.script.value.FunctionValue;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Supplier;
/* The expression evaluators exception class. */
public class ExpressionException extends RuntimeException implements ResolvedException
{
public final Context context;
+ public final Tokenizer.Token token;
public final List stack = new ArrayList<>();
+ private final Supplier lazyMessage;
+ private String cachedMessage = null;
public ExpressionException(Context c, Expression e, String message)
{
- super(makeMessage(c, e, null, message));
- context = c;
+ this(c, e, Tokenizer.Token.NONE, message);
}
public ExpressionException(Context c, Expression e, Tokenizer.Token t, String message)
{
- super(makeMessage(c, e, t, message));
+ super("Error");
+ lazyMessage = () -> makeMessage(c, e, t, message);
+ token = t;
context = c;
}
public ExpressionException(Context c, Expression e, Tokenizer.Token t, String message, List stack)
{
- super(makeMessage(c, e, t, message));
+ super("Error");
this.stack.addAll(stack);
+ lazyMessage = () -> makeMessage(c, e, t, message);
+ token = t;
+ context = c;
+ }
+
+ public ExpressionException(Context c, Expression e, Tokenizer.Token t, Supplier messageSupplier, List stack)
+ {
+ super("Error");
+ this.stack.addAll(stack);
+ lazyMessage = () -> makeMessage(c, e, t, messageSupplier.get());
+ token = t;
context = c;
}
@@ -68,4 +84,13 @@ public class ExpressionException extends RuntimeException implements ResolvedExc
}
return String.join("\n", errorMaker.apply(e, t, message));
}
+
+ @Override
+ public String getMessage() {
+ if (cachedMessage == null)
+ {
+ cachedMessage = lazyMessage.get();
+ }
+ return cachedMessage;
+ }
}
diff --git a/src/main/java/carpet/script/exception/InternalExpressionException.java b/src/main/java/carpet/script/exception/InternalExpressionException.java
index 6efb96c6..1ebab8f3 100644
--- a/src/main/java/carpet/script/exception/InternalExpressionException.java
+++ b/src/main/java/carpet/script/exception/InternalExpressionException.java
@@ -1,5 +1,8 @@
package carpet.script.exception;
+import carpet.script.Context;
+import carpet.script.Expression;
+import carpet.script.Tokenizer;
import carpet.script.value.FunctionValue;
import java.util.ArrayList;
@@ -13,4 +16,20 @@ public class InternalExpressionException extends RuntimeException
{
super(message);
}
+
+ /**
+ * Promotes this simple exception into one with context and extra information.
+ *
+ *
Provides a cleaner way of handling similar exceptions, in this case
+ * {@link InternalExpressionException} and {@link ThrowStatement}
+ * @param c Context
+ * @param e Expression
+ * @param token Token
+ * @return The new {@link ExpressionException} (or {@link ProcessedThrowStatement}),
+ * depending on the implementation.
+ */
+ public ExpressionException promote(Context c, Expression e, Tokenizer.Token token)
+ {
+ return new ExpressionException(c, e, token, getMessage(), stack);
+ }
}
diff --git a/src/main/java/carpet/script/exception/ProcessedThrowStatement.java b/src/main/java/carpet/script/exception/ProcessedThrowStatement.java
new file mode 100644
index 00000000..977cf51f
--- /dev/null
+++ b/src/main/java/carpet/script/exception/ProcessedThrowStatement.java
@@ -0,0 +1,20 @@
+package carpet.script.exception;
+
+import java.util.List;
+
+import carpet.script.Context;
+import carpet.script.Expression;
+import carpet.script.Tokenizer.Token;
+import carpet.script.value.FunctionValue;
+import carpet.script.value.Value;
+
+public class ProcessedThrowStatement extends ExpressionException {
+ public final Throwables thrownExceptionType;
+ public final Value data;
+
+ public ProcessedThrowStatement(Context c, Expression e, Token token, List stack, Throwables thrownExceptionType, Value data) {
+ super(c, e, token, () -> "Unhandled "+thrownExceptionType.getId()+" exception: "+data.getString(), stack);
+ this.thrownExceptionType = thrownExceptionType;
+ this.data = data;
+ }
+}
diff --git a/src/main/java/carpet/script/exception/ThrowStatement.java b/src/main/java/carpet/script/exception/ThrowStatement.java
index 46c3fe2b..c61470cb 100644
--- a/src/main/java/carpet/script/exception/ThrowStatement.java
+++ b/src/main/java/carpet/script/exception/ThrowStatement.java
@@ -1,11 +1,54 @@
package carpet.script.exception;
+import carpet.script.Context;
+import carpet.script.Expression;
+import carpet.script.Tokenizer.Token;
+import carpet.script.value.StringValue;
import carpet.script.value.Value;
-public class ThrowStatement extends ExitStatement
+public class ThrowStatement extends InternalExpressionException
{
- public ThrowStatement(Value value)
+ private final Throwables type;
+ private final Value data;
+
+ /**
+ * Creates a throw exception from a value, and assigns it a specified message.
+ * To be used when throwing from Scarpet's {@code throw} function
+ * @param data The value to pass
+ * @param type Exception type
+ */
+ public ThrowStatement(Value data, Throwables type)
{
- super(value);
+ super(type.getId());
+ this.data = data;
+ this.type = type;
+ }
+
+ public ThrowStatement(Value data, Throwables parent, String subtype)
+ {
+ super(subtype);
+ this.data = data;
+ this.type = new Throwables(subtype, parent);
+ }
+
+ /**
+ * Creates a throw exception.
+ * Conveniently creates a value from the {@code value} String
+ * to be used easily in Java code
+ * @param message The message to display when not handled
+ * @param type An {@link Throwables} containing the inheritance data
+ * for this exception. When throwing from Java,
+ * those exceptions should be pre-registered.
+ */
+ public ThrowStatement(String message, Throwables type)
+ {
+ super(type.getId());
+ this.data = StringValue.of(message);
+ this.type = type;
+ }
+
+ @Override
+ public ExpressionException promote(Context c, Expression e, Token token) {
+ return new ProcessedThrowStatement(c, e, token, stack, type, data);
}
}
diff --git a/src/main/java/carpet/script/exception/Throwables.java b/src/main/java/carpet/script/exception/Throwables.java
new file mode 100644
index 00000000..22857a41
--- /dev/null
+++ b/src/main/java/carpet/script/exception/Throwables.java
@@ -0,0 +1,89 @@
+package carpet.script.exception;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class contains default Scarpet catchable types, as well as their inheritance and
+ * methods to check whether filters are compatible with those.
+ */
+public class Throwables {
+ private final String id;
+ private final Throwables parent;
+
+ private static final Map byId = new HashMap<>();
+
+ public static final Throwables THROWN_EXCEPTION_TYPE = register("exception", null);
+ public static final Throwables VALUE_EXCEPTION = register("value_exception", THROWN_EXCEPTION_TYPE);
+ public static final Throwables UNKNOWN_ITEM = register("unknown_item", VALUE_EXCEPTION);
+ public static final Throwables UNKNOWN_BLOCK = register("unknown_block", VALUE_EXCEPTION);
+ public static final Throwables UNKNOWN_BIOME = register("unknown_biome", VALUE_EXCEPTION);
+ public static final Throwables UNKNOWN_SOUND = register("unknown_sound", VALUE_EXCEPTION);
+ public static final Throwables UNKNOWN_PARTICLE = register("unknown_particle", VALUE_EXCEPTION);
+ public static final Throwables UNKNOWN_POI = register("unknown_poi", 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_CRITERION = register("unknown_criterion", VALUE_EXCEPTION);
+ public static final Throwables IO_EXCEPTION = register("io_exception", THROWN_EXCEPTION_TYPE);
+ public static final Throwables NBT_READ = register("nbt_read_error", IO_EXCEPTION);
+ public static final Throwables JSON_READ = register("json_read_error", IO_EXCEPTION);
+ public static final Throwables USER_DEFINED = register("user_exception", THROWN_EXCEPTION_TYPE);
+
+ /**
+ * Creates an exception and registers it to be used as parent for
+ * user defined exceptions in Scarpet's throw function.
+ * Scarpet exceptions should have a top-level parent being {@link Throwables#THROWN_EXCEPTION_TYPE}
+ * @param id The value for the exception as a {@link String}.
+ * @param parent The parent of the exception being created, or null if top-level
+ * @return The created exception
+ */
+ public static Throwables register(String id, Throwables parent)
+ {
+ Throwables exc = new Throwables(id, parent);
+ byId.put(id, exc);
+ return exc;
+ }
+
+ /**
+ * Creates a new exception.
+ *
Not suitable for creating exceptions that can't be caught.
+ * Use an {@link InternalExpressionException} for that
+ * @param id The exception's value as a {@link String}
+ */
+ public Throwables(String id, Throwables parent)
+ {
+ this.id = id;
+ this.parent = parent;
+ }
+
+ public static Throwables getTypeForException(String type)
+ {
+ Throwables properType = byId.get(type);
+ if (properType == null) throw new InternalExpressionException("Unknown exception type: "+type);
+ return properType;
+ }
+
+ /**
+ * Checks whether the given filter matches an instance of this exception, by checking equality
+ * with itself and possible parents.
+ * @param filter The type to check against
+ * @return Whether or not the given value matches this exception's hierarchy
+ */
+ public boolean isRelevantFor(String filter)
+ {
+ return (id.equals(filter) || (parent != null && parent.isRelevantFor(filter)));
+ }
+
+ public boolean isUserException()
+ {
+ return this == USER_DEFINED || parent == USER_DEFINED;
+ }
+
+ /**
+ * Returns the throwable type
+ * @return The type of this exception
+ */
+ public String getId() {
+ return id;
+ }
+}
diff --git a/src/main/java/carpet/script/language/ControlFlow.java b/src/main/java/carpet/script/language/ControlFlow.java
index 524cf4ca..e094309e 100644
--- a/src/main/java/carpet/script/language/ControlFlow.java
+++ b/src/main/java/carpet/script/language/ControlFlow.java
@@ -5,8 +5,17 @@ import carpet.script.Expression;
import carpet.script.LazyValue;
import carpet.script.exception.ExitStatement;
import carpet.script.exception.InternalExpressionException;
+import carpet.script.exception.ProcessedThrowStatement;
import carpet.script.exception.ThrowStatement;
+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 com.google.common.collect.ImmutableMap;
+
+import java.util.stream.Collectors;
public class ControlFlow {
public static void apply(Expression expression) // public just to get the javadoc right
@@ -42,26 +51,98 @@ public class ControlFlow {
expression.addFunction("exit", (lv) -> { throw new ExitStatement(lv.size()==0?Value.NULL:lv.get(0)); });
- expression.addFunction("throw", (lv)-> {throw new ThrowStatement(lv.size()==0?Value.NULL:lv.get(0)); });
+ expression.addLazyFunction("throw", -1, (c, t, lv)->
+ {
+ switch (lv.size())
+ {
+ case 0:
+ throw new ThrowStatement(Value.NULL, Throwables.USER_DEFINED);
+ case 1:
+ throw new ThrowStatement(lv.get(0).evalValue(c), Throwables.USER_DEFINED );
+ case 2:
+ throw new ThrowStatement(lv.get(1).evalValue(c), Throwables.getTypeForException(lv.get(0).evalValue(c).getString()));
+ case 3:
+ throw new ThrowStatement(lv.get(2).evalValue(c), Throwables.getTypeForException(lv.get(1).evalValue(c).getString()), lv.get(0).evalValue(c).getString());
+ default:
+ throw new InternalExpressionException("throw() can't accept more than 3 parameters");
+ }
+ });
expression.addLazyFunction("try", -1, (c, t, lv) ->
{
if (lv.size()==0)
- throw new InternalExpressionException("'try' needs at least an expression block");
+ throw new InternalExpressionException("'try' needs at least an expression block, and either a catch_epr, or a number of pairs of filters and catch_expr");
try
{
Value retval = lv.get(0).evalValue(c, t);
return (c_, t_) -> retval;
}
- catch (ThrowStatement ret)
+ catch (ProcessedThrowStatement ret)
{
if (lv.size() == 1)
+ {
+ if (!ret.thrownExceptionType.isUserException())
+ throw ret;
return (c_, t_) -> Value.NULL;
+ }
+ if (lv.size() > 3 && lv.size() % 2 == 0)
+ {
+ throw new InternalExpressionException("Try-catch block needs the code to run, and either a catch expression for user thrown exceptions, or a number of pairs of filters and catch expressions");
+ }
+
+ Value val = null; // This is always assigned at some point, just the compiler doesn't know
+
LazyValue __ = c.getVariable("_");
- c.setVariable("_", (__c, __t) -> ret.retval.reboundedTo("_"));
- Value val = lv.get(1).evalValue(c, t);
- c.setVariable("_",__);
- return (c_, t_) -> val;
+ 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().filter(e -> !e.getKey().equals("_trace")).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 (ret.thrownExceptionType.isUserException())
+ val = lv.get(1).evalValue(c, t);
+ }
+ else
+ {
+ int pointer = 1;
+ while (pointer < lv.size() -1)
+ {
+ if (ret.thrownExceptionType.isRelevantFor(lv.get(pointer).evalValue(c).getString()))
+ {
+ val = lv.get(pointer + 1).evalValue(c, t);
+ break;
+ }
+ pointer += 2;
+ }
+ }
+ c.setVariable("_", __);
+ if (_trace != null)
+ c.setVariable("_trace", _trace);
+ else
+ c.delVariable("_trace");
+ if (val == null) // not handled
+ {
+ throw ret;
+ }
+ Value retval = val;
+ return (c_, t_) -> retval;
}
});
}
diff --git a/src/main/java/carpet/script/utils/ShapeDispatcher.java b/src/main/java/carpet/script/utils/ShapeDispatcher.java
index 871d55aa..03b7034b 100644
--- a/src/main/java/carpet/script/utils/ShapeDispatcher.java
+++ b/src/main/java/carpet/script/utils/ShapeDispatcher.java
@@ -4,6 +4,8 @@ import carpet.CarpetSettings;
import carpet.network.ServerNetworkHandler;
import carpet.script.CarpetContext;
import carpet.script.exception.InternalExpressionException;
+import carpet.script.exception.ThrowStatement;
+import carpet.script.exception.Throwables;
import carpet.script.language.Sys;
import carpet.script.value.BlockValue;
import carpet.script.value.EntityValue;
@@ -156,7 +158,7 @@ public class ShapeDispatcher
}
catch (CommandSyntaxException e)
{
- throw new InternalExpressionException("No such particle: "+name);
+ throw new ThrowStatement(name, Throwables.UNKNOWN_PARTICLE);
}
particleCache.put(name, particle);
return particle;
diff --git a/src/main/java/carpet/script/value/BlockValue.java b/src/main/java/carpet/script/value/BlockValue.java
index c2825f26..e5f6a902 100644
--- a/src/main/java/carpet/script/value/BlockValue.java
+++ b/src/main/java/carpet/script/value/BlockValue.java
@@ -2,6 +2,9 @@ package carpet.script.value;
import carpet.script.CarpetContext;
import carpet.script.exception.InternalExpressionException;
+import carpet.script.exception.ThrowStatement;
+import carpet.script.exception.Throwables;
+
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.block.BlockState;
@@ -73,7 +76,7 @@ public class BlockValue extends Value
catch (CommandSyntaxException ignored)
{
}
- throw new InternalExpressionException("Cannot parse block: "+str);
+ throw new ThrowStatement(str, Throwables.UNKNOWN_BLOCK);
}
public static BlockPos locateBlockPos(CarpetContext c, int xpos, int ypos, int zpos)
diff --git a/src/main/java/carpet/script/value/FunctionValue.java b/src/main/java/carpet/script/value/FunctionValue.java
index 6517dd66..61e97b18 100644
--- a/src/main/java/carpet/script/value/FunctionValue.java
+++ b/src/main/java/carpet/script/value/FunctionValue.java
@@ -215,7 +215,6 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction
}
Value retVal;
- boolean rethrow = false;
try
{
retVal = body.evalValue(newFrame, type); // todo not sure if we need to propagete type / consider boolean context in defined functions - answer seems ye
@@ -228,15 +227,6 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction
{
retVal = returnStatement.retval;
}
- catch (ThrowStatement throwStatement) // might not be really necessary
- {
- retVal = throwStatement.retval;
- rethrow = true;
- }
- if (rethrow)
- {
- throw new ThrowStatement(retVal);
- }
Value otherRetVal = retVal;
return (cc, tt) -> otherRetVal;
}
diff --git a/src/main/java/carpet/script/value/NBTSerializableValue.java b/src/main/java/carpet/script/value/NBTSerializableValue.java
index 4b9dce80..9fef4954 100644
--- a/src/main/java/carpet/script/value/NBTSerializableValue.java
+++ b/src/main/java/carpet/script/value/NBTSerializableValue.java
@@ -4,6 +4,8 @@ import carpet.fakes.InventoryBearerInterface;
import carpet.script.CarpetContext;
import carpet.script.LazyValue;
import carpet.script.exception.InternalExpressionException;
+import carpet.script.exception.ThrowStatement;
+import carpet.script.exception.Throwables;
import carpet.script.utils.EquipmentInventory;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
@@ -297,7 +299,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
}
catch (CommandSyntaxException e)
{
- throw new InternalExpressionException("Incorrect item: "+itemString);
+ throw new ThrowStatement(itemString, Throwables.UNKNOWN_ITEM);
}
}
diff --git a/src/main/java/carpet/script/value/ValueConversions.java b/src/main/java/carpet/script/value/ValueConversions.java
index 1874238d..51d431af 100644
--- a/src/main/java/carpet/script/value/ValueConversions.java
+++ b/src/main/java/carpet/script/value/ValueConversions.java
@@ -2,6 +2,8 @@ package carpet.script.value;
import carpet.fakes.BlockPredicateInterface;
import carpet.script.exception.InternalExpressionException;
+import carpet.script.exception.ThrowStatement;
+import carpet.script.exception.Throwables;
import carpet.utils.BlockInfo;
import net.minecraft.block.BlockState;
import net.minecraft.block.MapColor;
@@ -158,7 +160,7 @@ public class ValueConversions
}
}
if (dim == null)
- throw new InternalExpressionException("Incorrect dimension string: "+dimString);
+ throw new ThrowStatement(dimString, Throwables.UNKNOWN_DIMENSION);
return server.getWorld(dim);
}
}