From a155494d1183835a2d316bce3f808c12e282c5db Mon Sep 17 00:00:00 2001 From: altrisi Date: Tue, 12 Jan 2021 19:24:16 +0100 Subject: [PATCH 01/16] Allow some exceptions to be handled --- docs/scarpet/api/Auxiliary.md | 15 +++++ docs/scarpet/api/BlocksAndWorldAccess.md | 21 +++++++ docs/scarpet/api/Inventories.md | 12 +++- docs/scarpet/api/Scoreboard.md | 2 + .../language/FunctionsAndControlFlow.md | 42 ++++++++++--- src/main/java/carpet/script/Expression.java | 7 +-- .../java/carpet/script/api/Auxiliary.java | 29 +++++++-- .../java/carpet/script/api/Inventories.java | 24 ++------ .../java/carpet/script/api/Scoreboards.java | 3 +- .../java/carpet/script/api/WorldAccess.java | 26 ++++---- .../script/exception/ExpressionException.java | 21 +++++-- .../InternalExpressionException.java | 19 ++++++ .../exception/ProcessedThrowStatement.java | 20 ++++++ .../script/exception/ThrowStatement.java | 61 ++++++++++++++++++- .../carpet/script/language/ControlFlow.java | 36 +++++++++-- .../carpet/script/utils/ShapeDispatcher.java | 3 +- .../java/carpet/script/value/BlockValue.java | 4 +- .../carpet/script/value/FunctionValue.java | 10 --- .../script/value/NBTSerializableValue.java | 3 +- .../carpet/script/value/ValueConversions.java | 3 +- 20 files changed, 283 insertions(+), 78 deletions(-) create mode 100644 src/main/java/carpet/script/exception/ProcessedThrowStatement.java diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index cede1808..489017bd 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_exception`: When failed to read NBT file +- `json_read_exception`: When failed to read JSON file. The `_msg` variable 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_exception` 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.
@@ -448,6 +461,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 given a dimension as a String and it 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 a7419d0f..b3ab8745 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 block from `state` doesn't exist.
+
 
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 `block` doesn't exist.
+
 
 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_type` if POI type 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 biome 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 `block` comes from a String and doesn't exist.
+
 
 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` comes from a String and 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 
@@ -348,6 +364,8 @@ and values < 0, below sea level.
 * `'features'`: list of features that generate in the biome, grouped by generation steps
 * `'structures'`: list of structures that generate in the biome.
 
+Throws `unknown_block` if `block` comes from a String and doesn't exist. 
+
 ### `solid(pos)`
 
 Boolean function, true if the block is solid.
@@ -716,6 +734,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)`
 
@@ -749,6 +768,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..f44ffcbc 100644
--- a/docs/scarpet/language/FunctionsAndControlFlow.md
+++ b/docs/scarpet/language/FunctionsAndControlFlow.md
@@ -234,15 +234,41 @@ 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, id_filter?, catch_expr(_, _msg)?)`
 
-`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` function evaluates expression, and continues further unless an exception is thrown anywhere inside `expr`, 
+it being produced by `throw` or a catchable exception is thrown by Carpet. In that case the `catch_expr` is evaluated with 
+`_` set to the id of the exception and `_msg` set to the exception message. If `id_filter` is present, only exceptions
+with an id equal to that value will be catched, and any other exception will continue up the stack. 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 mechanic accepts skipping catch expression - then try returns `null`. 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.
+
+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 resourcepacks or mods, or when an error that
+is out of the scope of the programmer occurs, like problems when reading a file.
+
+In this documentation, those are documented in at least most of the functions that throw them, and the current list of exceptions
+is the following:
+- `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
+- `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
+
+### `throw(id?, message?)`
+
+Throws an exception that can be catched in a `try` block (see above). If ran without arguments, it will pass `null`
+as the value to the `catch_expr`. `message` is the message to show to chat or console in case the `throw` is 
+unhandled.
 
 ### `if(cond, expr, cond?, expr?, ..., default?)`
 
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/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java
index a0268cc7..74d152fe 100644
--- a/src/main/java/carpet/script/api/Auxiliary.java
+++ b/src/main/java/carpet/script/api/Auxiliary.java
@@ -19,6 +19,7 @@ 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.utils.FixedCommandSource;
 import carpet.script.utils.ScarpetJsonDeserializer;
 import carpet.script.utils.ShapeDispatcher;
@@ -62,6 +63,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.math.BlockPos;
 import net.minecraft.util.math.ChunkPos;
 import net.minecraft.util.math.EulerAngle;
@@ -130,7 +132,7 @@ public class Auxiliary {
             Identifier soundName = new Identifier(lv.get(0).evalValue(c).getString());
             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("No such sound: "+soundName.getPath(), ThrowStatement.UNKNOWN_SOUND);
             float volume = 1.0F;
             float pitch = 1.0F;
             SoundCategory mixer = SoundCategory.MASTER;
@@ -820,7 +822,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("Couldn't read NBT data", ThrowStatement.NBT_READ_EXCEPTION); 
+                }
+
                 if (state == null) return LazyValue.NULL;
                 retVal = new NBTSerializableValue(state);
             }
@@ -833,10 +843,10 @@ 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("Failed to read JSON file: "+exc.getMessage(), ThrowStatement.JSON_READ_EXCEPTION);
                 }
                 Value parsedJson = gson.fromJson(json, Value.class);
                 if (parsedJson == null)
@@ -924,7 +934,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("Failed to read App data", ThrowStatement.NBT_READ_EXCEPTION);
+            }
             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 fb4983ae..713b7ac2 100644
--- a/src/main/java/carpet/script/api/Inventories.java
+++ b/src/main/java/carpet/script/api/Inventories.java
@@ -6,6 +6,7 @@ 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.value.ListValue;
 import carpet.script.value.NBTSerializableValue;
 import carpet.script.value.NullValue;
@@ -119,14 +120,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 +191,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("Incorrect item: "+itemStr, ThrowStatement.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 d7cdf1e8..73097127 100644
--- a/src/main/java/carpet/script/api/Scoreboards.java
+++ b/src/main/java/carpet/script/api/Scoreboards.java
@@ -6,6 +6,7 @@ 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.value.EntityValue;
 import carpet.script.value.ListValue;
 import carpet.script.value.NullValue;
@@ -131,7 +132,7 @@ public class Scoreboards {
                 criterion = ScoreboardCriterion.createStatCriterion(critetionName).orElse(null);
                 if (criterion==null)
                 {
-                    throw new InternalExpressionException("Unknown scoreboard criterion: "+critetionName);
+                    throw new ThrowStatement("Unknown scoreboard criterion: "+critetionName, ThrowStatement.UNKNOWN_CRITERION);
                 }
             }
 
diff --git a/src/main/java/carpet/script/api/WorldAccess.java b/src/main/java/carpet/script/api/WorldAccess.java
index d15da361..eee5ecd4 100644
--- a/src/main/java/carpet/script/api/WorldAccess.java
+++ b/src/main/java/carpet/script/api/WorldAccess.java
@@ -19,6 +19,7 @@ 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.utils.BiomeInfo;
 import carpet.script.utils.WorldTools;
 import carpet.script.value.BlockValue;
@@ -336,8 +337,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("Unknown POI type", ThrowStatement.UNKNOWN_POI_TYPE));
                     condition = (tt) -> tt == type;
                 }
                 if (locator.offset + 2 < lv.size())
@@ -391,9 +392,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("Unknown POI type: "+poiTypeString, ThrowStatement.UNKNOWN_POI_TYPE));
             int occupancy = 0;
             if (locator.offset + 1 < lv.size())
             {
@@ -872,9 +872,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("Incorrect item: " + itemString, ThrowStatement.UNKNOWN_ITEM));
                 }
             }
             CompoundTag tag = null;
@@ -1180,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("Unknown biome: "+biomeName, ThrowStatement.UNKNOWN_BIOME));
             boolean doImmediateUpdate = true;
             if (lv.size() > locator.offset+1)
             {
@@ -1251,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("Unknown structure: " + reqString, ThrowStatement.UNKNOWN_STRUCTURE));
                 }
                 if (lv.size() > locator.offset+1)
                 {
@@ -1332,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("Unknown structure: "+structureName, ThrowStatement.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..ec72fccb 100644
--- a/src/main/java/carpet/script/exception/ExpressionException.java
+++ b/src/main/java/carpet/script/exception/ExpressionException.java
@@ -8,28 +8,32 @@ 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 List stack = new ArrayList<>();
+    private final Supplier lazyStacktrace;
+    private String cachedMessage = null;
 
     public ExpressionException(Context c, Expression e, String message)
     {
-        super(makeMessage(c, e, null, message));
-        context = c;
+        this(c, e, null, message);
     }
 
     public ExpressionException(Context c, Expression e, Tokenizer.Token t, String message)
     {
-        super(makeMessage(c, e, t, message));
+        super("Error");
+        lazyStacktrace = () -> makeMessage(c, e, t, message);
         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);
+        lazyStacktrace = () -> makeMessage(c, e, t, message); 
         context = c;
     }
 
@@ -68,4 +72,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 = lazyStacktrace.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..9e5a9983 --- /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 Value exceptionName; + public final String message; + + public ProcessedThrowStatement(Context c, Expression e, Token token, String message, List stack, Value exceptionName) { + super(c, e, token, "Unhandled exception: "+message, stack); + this.exceptionName = exceptionName; + this.message = message; + } +} diff --git a/src/main/java/carpet/script/exception/ThrowStatement.java b/src/main/java/carpet/script/exception/ThrowStatement.java index 46c3fe2b..db32a335 100644 --- a/src/main/java/carpet/script/exception/ThrowStatement.java +++ b/src/main/java/carpet/script/exception/ThrowStatement.java @@ -1,11 +1,68 @@ 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 static final String UNKNOWN_ITEM = "unknown_item"; + public static final String UNKNOWN_BLOCK = "unknown_block"; + public static final String UNKNOWN_BIOME = "unknown_biome"; + public static final String UNKNOWN_SOUND = "unknown_sound"; + public static final String UNKNOWN_PARTICLE = "unknown_particle"; + public static final String UNKNOWN_POI_TYPE = "unknown_poi_type"; + public static final String UNKNOWN_DIMENSION = "unknown_dimension"; + public static final String UNKNOWN_STRUCTURE = "unknown_structure"; + public static final String UNKNOWN_CRITERION = "unknown_criterion"; + public static final String NBT_READ_EXCEPTION = "nbt_read_exception"; + public static final String JSON_READ_EXCEPTION = "json_read_exception"; + public final Value retval; + /** + * Creates a throw exception from a value. + * That value will also be used as the message.
+ * To use when throwing from Scarpet's {@code throw} + * @param value The value to pass + */ public ThrowStatement(Value value) { - super(value); + super(value.getString()); + retval = value; + } + + /** + * Creates a throw exception from a value, and + * assigns it a specified message.
+ * To use when throwing from Scarpet's {@code throw} + * function + * @param message The message to display if uncaught + * @param value The value to pass + */ + public ThrowStatement(String message, Value value) + { + super(message); + retval = value; + } + + /** + * 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 value A String that will be converted + * to a value to pass to {@code catch} + * blocks + */ + public ThrowStatement(String message, String value) + { + super(message); + retval = new StringValue(value); + } + + @Override + public ExpressionException promote(Context c, Expression e, Token token) { + return new ProcessedThrowStatement(c, e, token, getMessage(), stack, retval); } } diff --git a/src/main/java/carpet/script/language/ControlFlow.java b/src/main/java/carpet/script/language/ControlFlow.java index 524cf4ca..309f4f5b 100644 --- a/src/main/java/carpet/script/language/ControlFlow.java +++ b/src/main/java/carpet/script/language/ControlFlow.java @@ -5,7 +5,9 @@ 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.value.StringValue; import carpet.script.value.Value; public class ControlFlow { @@ -42,7 +44,20 @@ 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("No further information", Value.NULL); + case 1: + throw new ThrowStatement(lv.get(0).evalValue(c)); + case 2: + throw new ThrowStatement(lv.get(1).evalValue(c).getString(), lv.get(0).evalValue(c)); + default: + throw new InternalExpressionException("throw() can't accept more than 2 parameters"); + } + }); expression.addLazyFunction("try", -1, (c, t, lv) -> { @@ -53,14 +68,27 @@ public class ControlFlow { Value retval = lv.get(0).evalValue(c, t); return (c_, t_) -> retval; } - catch (ThrowStatement ret) + catch (ProcessedThrowStatement ret) { if (lv.size() == 1) return (c_, t_) -> Value.NULL; LazyValue __ = c.getVariable("_"); - c.setVariable("_", (__c, __t) -> ret.retval.reboundedTo("_")); - Value val = lv.get(1).evalValue(c, t); + c.setVariable("_", (__c, __t) -> ret.exceptionName.reboundedTo("_")); + LazyValue __msg = c.getVariable("_msg"); + c.setVariable("_msg", (__c, __t) -> StringValue.of(ret.message).reboundedTo("_msg")); + Value val; + if (lv.size() == 3) + { + if (!ret.exceptionName.equals(lv.get(1).evalValue(c))) + throw ret; + val = lv.get(2).evalValue(c, t); + } + else + { + val = lv.get(1).evalValue(c, t); + } c.setVariable("_",__); + c.setVariable("_msg", __msg); return (c_, t_) -> val; } }); diff --git a/src/main/java/carpet/script/utils/ShapeDispatcher.java b/src/main/java/carpet/script/utils/ShapeDispatcher.java index 761b8420..ac24c46f 100644 --- a/src/main/java/carpet/script/utils/ShapeDispatcher.java +++ b/src/main/java/carpet/script/utils/ShapeDispatcher.java @@ -4,6 +4,7 @@ import carpet.CarpetSettings; import carpet.network.ServerNetworkHandler; import carpet.script.CarpetContext; import carpet.script.exception.InternalExpressionException; +import carpet.script.exception.ThrowStatement; import carpet.script.language.Sys; import carpet.script.value.BlockValue; import carpet.script.value.EntityValue; @@ -156,7 +157,7 @@ public class ShapeDispatcher } catch (CommandSyntaxException e) { - throw new InternalExpressionException("No such particle: "+name); + throw new ThrowStatement("No such particle: "+name, ThrowStatement.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 e883f27f..56846d9b 100644 --- a/src/main/java/carpet/script/value/BlockValue.java +++ b/src/main/java/carpet/script/value/BlockValue.java @@ -2,6 +2,8 @@ package carpet.script.value; import carpet.script.CarpetContext; import carpet.script.exception.InternalExpressionException; +import carpet.script.exception.ThrowStatement; + import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.minecraft.block.BlockState; @@ -73,7 +75,7 @@ public class BlockValue extends Value catch (CommandSyntaxException ignored) { } - throw new InternalExpressionException("Cannot parse block: "+str); + throw new ThrowStatement("Cannot parse block: "+str, ThrowStatement.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 877300dc..1a790c2b 100644 --- a/src/main/java/carpet/script/value/NBTSerializableValue.java +++ b/src/main/java/carpet/script/value/NBTSerializableValue.java @@ -4,6 +4,7 @@ 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.utils.EquipmentInventory; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -297,7 +298,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa } catch (CommandSyntaxException e) { - throw new InternalExpressionException("Incorrect item: "+itemString); + throw new ThrowStatement("Incorrect item: "+itemString, ThrowStatement.UNKNOWN_ITEM); } } diff --git a/src/main/java/carpet/script/value/ValueConversions.java b/src/main/java/carpet/script/value/ValueConversions.java index b3344f40..9fc4e62f 100644 --- a/src/main/java/carpet/script/value/ValueConversions.java +++ b/src/main/java/carpet/script/value/ValueConversions.java @@ -2,6 +2,7 @@ package carpet.script.value; import carpet.fakes.BlockPredicateInterface; import carpet.script.exception.InternalExpressionException; +import carpet.script.exception.ThrowStatement; import carpet.utils.BlockInfo; import net.minecraft.block.BlockState; import net.minecraft.block.MaterialColor; @@ -158,7 +159,7 @@ public class ValueConversions } } if (dim == null) - throw new InternalExpressionException("Incorrect dimension string: "+dimString); + throw new ThrowStatement("Incorrect dimension string: "+dimString, ThrowStatement.UNKNOWN_DIMENSION); return server.getWorld(dim); } } From 86dabe008047059bad3f782d9365894a92e962dc Mon Sep 17 00:00:00 2001 From: altrisi Date: Sun, 21 Feb 2021 00:40:50 +0100 Subject: [PATCH 02/16] Add exception hierarchy --- .../language/FunctionsAndControlFlow.md | 57 ++++---- .../java/carpet/script/api/Auxiliary.java | 9 +- .../java/carpet/script/api/Inventories.java | 3 +- .../java/carpet/script/api/Scoreboards.java | 3 +- .../java/carpet/script/api/WorldAccess.java | 13 +- .../exception/ProcessedThrowStatement.java | 7 +- .../script/exception/ThrowStatement.java | 41 +++--- .../carpet/script/exception/Throwables.java | 127 ++++++++++++++++++ .../carpet/script/language/ControlFlow.java | 18 ++- .../carpet/script/utils/ShapeDispatcher.java | 3 +- .../java/carpet/script/value/BlockValue.java | 3 +- .../script/value/NBTSerializableValue.java | 3 +- .../carpet/script/value/ValueConversions.java | 3 +- 13 files changed, 216 insertions(+), 74 deletions(-) create mode 100644 src/main/java/carpet/script/exception/Throwables.java diff --git a/docs/scarpet/language/FunctionsAndControlFlow.md b/docs/scarpet/language/FunctionsAndControlFlow.md index f44ffcbc..28a03fb0 100644 --- a/docs/scarpet/language/FunctionsAndControlFlow.md +++ b/docs/scarpet/language/FunctionsAndControlFlow.md @@ -237,39 +237,50 @@ It terminates entire program passing `expr` as the result of the program executi ### `try(expr, id_filter?, catch_expr(_, _msg)?)` `try` function evaluates expression, and continues further unless an exception is thrown anywhere inside `expr`, -it being produced by `throw` or a catchable exception is thrown by Carpet. In that case the `catch_expr` is evaluated with -`_` set to the id of the exception and `_msg` set to the exception message. If `id_filter` is present, only exceptions -with an id equal to that value will be catched, and any other exception will continue up the stack. It is recommended to filter +it being produced by `throw` or a catchable exception being thrown by Carpet. In that case the `catch_expr` is evaluated with +`_` set to the id of the exception and `_msg` set to the exception message. If an `id_filter` is present, only exceptions +with an id equal to that value will be caught, and any other exception will continue up the stack. 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 mechanic accepts skipping catch expression - then try returns `null`. 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. +portion of a convoluted call stack and continue program execution. + +A `try` block will not only catch any exception whose id match the `id_filter`, but also any exception that has a parent with +their id being that one, recursively. 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 resourcepacks or mods, or when an error that -is out of the scope of the programmer occurs, like problems when reading a file. +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. -In this documentation, those are documented in at least most of the functions that throw them, and the current list of exceptions -is the following: -- `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 -- `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 +In this documentation, those are documented in at least most of the functions that throw them, and the current exception +hierarchy is the following: +- `exception`: This is the base exception. Every exception has this as its top-level parent + - `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?)` +### `throw(id?, message?, parent?)` -Throws an exception that can be catched in a `try` block (see above). If ran without arguments, it will pass `null` +Throws an exception that can be caught in a `try` block (see above). If ran without arguments, it will pass `null` as the value to the `catch_expr`. `message` is the message to show to chat or console in case the `throw` is unhandled. +By default, the parent of any exception thrown by this function is `user_defined_exception`. +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 +`exception` > `user_defined_exception` > `parent` > `id` + ### `if(cond, expr, cond?, expr?, ..., default?)` If statement is a function that takes a number of conditions that are evaluated one after another and if any of diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java index 74d152fe..df5bb8df 100644 --- a/src/main/java/carpet/script/api/Auxiliary.java +++ b/src/main/java/carpet/script/api/Auxiliary.java @@ -20,6 +20,7 @@ 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.ScarpetJsonDeserializer; import carpet.script.utils.ShapeDispatcher; @@ -132,7 +133,7 @@ public class Auxiliary { Identifier soundName = new Identifier(lv.get(0).evalValue(c).getString()); Vector3Argument locator = Vector3Argument.findIn(cc, lv, 1); if (Registry.SOUND_EVENT.get(soundName) == null) - throw new ThrowStatement("No such sound: "+soundName.getPath(), ThrowStatement.UNKNOWN_SOUND); + throw new ThrowStatement("No such sound: "+soundName.getPath(), Throwables.UNKNOWN_SOUND); float volume = 1.0F; float pitch = 1.0F; SoundCategory mixer = SoundCategory.MASTER; @@ -828,7 +829,7 @@ public class Auxiliary { state = ((CarpetScriptHost) c.host).readFileTag(fdesc.getLeft(), fdesc.getRight()); } catch (CrashException e) { - throw new ThrowStatement("Couldn't read NBT data", ThrowStatement.NBT_READ_EXCEPTION); + throw new ThrowStatement("Couldn't read NBT data", Throwables.NBT_READ); } if (state == null) return LazyValue.NULL; @@ -846,7 +847,7 @@ public class Auxiliary { Throwable exc = e; if(e.getCause() != null) exc = e.getCause(); - throw new ThrowStatement("Failed to read JSON file: "+exc.getMessage(), ThrowStatement.JSON_READ_EXCEPTION); + throw new ThrowStatement("Failed to read JSON file: "+exc.getMessage(), Throwables.JSON_READ); } Value parsedJson = gson.fromJson(json, Value.class); if (parsedJson == null) @@ -940,7 +941,7 @@ public class Auxiliary { state = ((CarpetScriptHost)((CarpetContext)c).host).readFileTag(file, shared); } catch (CrashException e) { - throw new ThrowStatement("Failed to read App data", ThrowStatement.NBT_READ_EXCEPTION); + throw new ThrowStatement("Failed to read App data", Throwables.NBT_READ); } if (state == null) return (cc, tt) -> Value.NULL; diff --git a/src/main/java/carpet/script/api/Inventories.java b/src/main/java/carpet/script/api/Inventories.java index 713b7ac2..01fa4ea8 100644 --- a/src/main/java/carpet/script/api/Inventories.java +++ b/src/main/java/carpet/script/api/Inventories.java @@ -7,6 +7,7 @@ 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; @@ -192,7 +193,7 @@ public class Inventories { String itemStr = lv.get(0).evalValue(c).getString(); Item item; Identifier id = new Identifier(itemStr); - item = Registry.ITEM.getOrEmpty(id).orElseThrow(() -> new ThrowStatement("Incorrect item: "+itemStr, ThrowStatement.UNKNOWN_ITEM)); + item = Registry.ITEM.getOrEmpty(id).orElseThrow(() -> new ThrowStatement("Incorrect item: "+itemStr, Throwables.UNKNOWN_ITEM)); if (!item.hasRecipeRemainder()) return LazyValue.NULL; Value ret = new StringValue(NBTSerializableValue.nameFromRegistryId(Registry.ITEM.getId(item.getRecipeRemainder()))); diff --git a/src/main/java/carpet/script/api/Scoreboards.java b/src/main/java/carpet/script/api/Scoreboards.java index 73097127..c58f7c10 100644 --- a/src/main/java/carpet/script/api/Scoreboards.java +++ b/src/main/java/carpet/script/api/Scoreboards.java @@ -7,6 +7,7 @@ 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; @@ -132,7 +133,7 @@ public class Scoreboards { criterion = ScoreboardCriterion.createStatCriterion(critetionName).orElse(null); if (criterion==null) { - throw new ThrowStatement("Unknown scoreboard criterion: "+critetionName, ThrowStatement.UNKNOWN_CRITERION); + throw new ThrowStatement("Unknown scoreboard criterion: "+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 eee5ecd4..85367839 100644 --- a/src/main/java/carpet/script/api/WorldAccess.java +++ b/src/main/java/carpet/script/api/WorldAccess.java @@ -20,6 +20,7 @@ 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; @@ -338,7 +339,7 @@ public class WorldAccess { if (!"any".equals(poiType)) { PointOfInterestType type = Registry.POINT_OF_INTEREST_TYPE.getOrEmpty(new Identifier(poiType)) - .orElseThrow(() -> new ThrowStatement("Unknown POI type", ThrowStatement.UNKNOWN_POI_TYPE)); + .orElseThrow(() -> new ThrowStatement("Unknown POI type", Throwables.UNKNOWN_POI_TYPE)); condition = (tt) -> tt == type; } if (locator.offset + 2 < lv.size()) @@ -393,7 +394,7 @@ public class WorldAccess { } String poiTypeString = poi.getString().toLowerCase(Locale.ROOT); PointOfInterestType type = Registry.POINT_OF_INTEREST_TYPE.getOrEmpty(new Identifier(poiTypeString)) - .orElseThrow(() -> new ThrowStatement("Unknown POI type: "+poiTypeString, ThrowStatement.UNKNOWN_POI_TYPE)); + .orElseThrow(() -> new ThrowStatement("Unknown POI type: "+poiTypeString, Throwables.UNKNOWN_POI_TYPE)); int occupancy = 0; if (locator.offset + 1 < lv.size()) { @@ -873,7 +874,7 @@ public class WorldAccess { playerBreak = true; String itemString = val.getString(); item = Registry.ITEM.getOrEmpty(new Identifier(itemString)) - .orElseThrow(() -> new ThrowStatement("Incorrect item: " + itemString, ThrowStatement.UNKNOWN_ITEM)); + .orElseThrow(() -> new ThrowStatement("Incorrect item: " + itemString, Throwables.UNKNOWN_ITEM)); } } CompoundTag tag = null; @@ -1180,7 +1181,7 @@ public class WorldAccess { String biomeName = lv.get(locator.offset+0).evalValue(c).getString(); // from locatebiome command code Biome biome = cc.s.getMinecraftServer().getRegistryManager().get(Registry.BIOME_KEY).getOrEmpty(new Identifier(biomeName)) - .orElseThrow(() -> new ThrowStatement("Unknown biome: "+biomeName, ThrowStatement.UNKNOWN_BIOME)); + .orElseThrow(() -> new ThrowStatement("Unknown biome: "+biomeName, Throwables.UNKNOWN_BIOME)); boolean doImmediateUpdate = true; if (lv.size() > locator.offset+1) { @@ -1250,7 +1251,7 @@ public class WorldAccess { { String reqString = requested.getString(); structure = Registry.STRUCTURE_FEATURE.getOrEmpty(new Identifier(reqString)) - .orElseThrow(() -> new ThrowStatement("Unknown structure: " + reqString, ThrowStatement.UNKNOWN_STRUCTURE)); + .orElseThrow(() -> new ThrowStatement("Unknown structure: " + reqString, Throwables.UNKNOWN_STRUCTURE)); } if (lv.size() > locator.offset+1) { @@ -1330,7 +1331,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 ThrowStatement("Unknown structure: "+structureName, ThrowStatement.UNKNOWN_STRUCTURE); + if (configuredStructure == null) throw new ThrowStatement("Unknown structure: "+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/ProcessedThrowStatement.java b/src/main/java/carpet/script/exception/ProcessedThrowStatement.java index 9e5a9983..003b71b0 100644 --- a/src/main/java/carpet/script/exception/ProcessedThrowStatement.java +++ b/src/main/java/carpet/script/exception/ProcessedThrowStatement.java @@ -6,15 +6,14 @@ 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 Value exceptionName; + public final Throwables.Exception exception; public final String message; - public ProcessedThrowStatement(Context c, Expression e, Token token, String message, List stack, Value exceptionName) { + public ProcessedThrowStatement(Context c, Expression e, Token token, String message, List stack, Throwables.Exception exception) { super(c, e, token, "Unhandled exception: "+message, stack); - this.exceptionName = exceptionName; + this.exception = exception; this.message = message; } } diff --git a/src/main/java/carpet/script/exception/ThrowStatement.java b/src/main/java/carpet/script/exception/ThrowStatement.java index db32a335..b8cb4155 100644 --- a/src/main/java/carpet/script/exception/ThrowStatement.java +++ b/src/main/java/carpet/script/exception/ThrowStatement.java @@ -3,47 +3,35 @@ 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; +import static carpet.script.exception.Throwables.Exception; + public class ThrowStatement extends InternalExpressionException { - public static final String UNKNOWN_ITEM = "unknown_item"; - public static final String UNKNOWN_BLOCK = "unknown_block"; - public static final String UNKNOWN_BIOME = "unknown_biome"; - public static final String UNKNOWN_SOUND = "unknown_sound"; - public static final String UNKNOWN_PARTICLE = "unknown_particle"; - public static final String UNKNOWN_POI_TYPE = "unknown_poi_type"; - public static final String UNKNOWN_DIMENSION = "unknown_dimension"; - public static final String UNKNOWN_STRUCTURE = "unknown_structure"; - public static final String UNKNOWN_CRITERION = "unknown_criterion"; - public static final String NBT_READ_EXCEPTION = "nbt_read_exception"; - public static final String JSON_READ_EXCEPTION = "json_read_exception"; - public final Value retval; + private final Exception exception; /** * Creates a throw exception from a value. * That value will also be used as the message.
- * To use when throwing from Scarpet's {@code throw} + * To be used when throwing from Scarpet's {@code throw} function with a single argument * @param value The value to pass */ public ThrowStatement(Value value) { - super(value.getString()); - retval = value; + this(value, value, Value.NULL); } /** - * Creates a throw exception from a value, and - * assigns it a specified message.
- * To use when throwing from Scarpet's {@code throw} - * function + * 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 message The message to display if uncaught * @param value The value to pass + * @param parent A parent's name */ - public ThrowStatement(String message, Value value) + public ThrowStatement(Value message, Value value, Value parent) { - super(message); - retval = value; + super(message.getString()); + exception = new Exception(value, parent); } /** @@ -54,15 +42,16 @@ public class ThrowStatement extends InternalExpressionException * @param value A String that will be converted * to a value to pass to {@code catch} * blocks + * @param parent This exception's data */ - public ThrowStatement(String message, String value) + public ThrowStatement(String message, Exception exception) { super(message); - retval = new StringValue(value); + this.exception = exception; } @Override public ExpressionException promote(Context c, Expression e, Token token) { - return new ProcessedThrowStatement(c, e, token, getMessage(), stack, retval); + return new ProcessedThrowStatement(c, e, token, getMessage(), stack, exception); } } 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..4bdfeebe --- /dev/null +++ b/src/main/java/carpet/script/exception/Throwables.java @@ -0,0 +1,127 @@ +package carpet.script.exception; + +import java.util.HashMap; +import java.util.Map; + +import carpet.script.value.NullValue; +import carpet.script.value.StringValue; +import carpet.script.value.Value; + +/** + * This class contains default Scarpet catchable exception's ids, as well as their inheritance and + * methods to check whether filters are compatible with those. + * + * Exceptions here cannot be thrown as an actual {@link Throwable}, they are just part of the metadata for + * {@link ThrowStatement} + */ +public class Throwables { + private static final Map byValue = new HashMap<>(); + public static final Exception EXCEPTION = register("exception", null); + public static final Exception VALUE_EXCEPTION = register("value_exception", EXCEPTION); + public static final Exception UNKNOWN_ITEM = register("unknown_item", VALUE_EXCEPTION); + public static final Exception UNKNOWN_BLOCK = register("unknown_block", VALUE_EXCEPTION); + public static final Exception UNKNOWN_BIOME = register("unknown_biome", VALUE_EXCEPTION); + public static final Exception UNKNOWN_SOUND = register("unknown_sound", VALUE_EXCEPTION); + public static final Exception UNKNOWN_PARTICLE = register("unknown_particle", VALUE_EXCEPTION); + public static final Exception UNKNOWN_POI_TYPE = register("unknown_poi_type", VALUE_EXCEPTION); + public static final Exception UNKNOWN_DIMENSION = register("unknown_dimension", VALUE_EXCEPTION); + public static final Exception UNKNOWN_STRUCTURE = register("unknown_structure", VALUE_EXCEPTION); + public static final Exception UNKNOWN_CRITERION = register("unknown_criterion", VALUE_EXCEPTION); + public static final Exception FILE_READ_EXCEPTION = register("file_read_exception", EXCEPTION); + public static final Exception NBT_READ = register("nbt_read_exception", FILE_READ_EXCEPTION); + public static final Exception JSON_READ = register("json_read_exception", FILE_READ_EXCEPTION); + public static final Exception USER_DEFINED = register("user_defined_exception", EXCEPTION); + protected static final Exception ERROR = register("error", null); + + private Throwables() {} + + /** + * Creates an exception and registers it to be used as parent for + * user defined exceptions in Scarpet's throw function. + *

Scarpet exceptions should, in general, have a top-level parent being {@link Throwables#EXCEPTION} + * @param name The name for the exception. Will be converted into a {@link StringValue} + * @param parent The parent of the exception being created, or null if top-level + * @return The created exception + */ + protected static Exception register(String value, Exception parent) + { + Exception exc = new Exception(value, parent); + byValue.put(exc.getValue(), exc); + return exc; + } + + /** + * The data of a {@link ThrowStatement} exception. Supports creating with inheritance and + * checking whether a filter is compatible with them. + */ + public static class Exception { + private final Value value; + private final Exception parent; + private final boolean isError; + + /** + * Creates a new exception, with an optional parent. + *

To be used in Scarpet's throw command + *

Parent will default to {@link Throwables#USER_DEFINED} if provided + * parentValue meets Value#isNull, else it will create a new {@link Exception}, + * with its parent being {@link Throwables#USER_DEFINED}. + * @param name The name/id of the exception + * @param parentValue An optional Value matching the name of a parent exception. + *
Accepts a {@link NullValue}, but not a null + */ + public Exception(Value value, Value parentValue) + { + if (byValue.containsKey(value)) + throw new InternalExpressionException("Exception id can't be the same as existing exception. Use it as parent instead"); + this.value = value; + if (!parentValue.isNull()) + { + Exception possibleParent = byValue.get(parentValue); + parent = possibleParent == null ? new Exception(parentValue, Value.NULL) : possibleParent; + } else + parent = USER_DEFINED; + isError = this.parent == ERROR; + } + + /** + * Creates a new exception. + *

Not suitable for creating exceptions that can't be caught. + * Use an {@link InternalExpressionException} for that + * @param name The exception's name/id + * @param parent The parent exception, or null if no parent is available + */ + protected Exception(String value, Exception parent) + { + this.value = StringValue.of(value); + this.parent = parent; + this.isError = false; + } + + /** + * Checks whether the given filter matches an instance of this exception, including checking equality + * with itself and possible parents. + * @param filter The value to check against + * @return Whether or not the given value matches this exception's hierarchy + */ + public boolean isInstance(Value filter) { + return getValue().equals(filter) || (parent != null && parent.isInstance(filter)); + } + + /** + * Returns the {@link Value} provided when creating this exception + * @return The {@link Value} of this exception + */ + public Value getValue() { + return value; + } + + /** + * Returns whether this exception has been defined as an error. + *

Error exceptions cannot be caught in Scarpet + * @return Whether this exception has been defined as an error + */ + public boolean isError() { + return isError; + } + } +} diff --git a/src/main/java/carpet/script/language/ControlFlow.java b/src/main/java/carpet/script/language/ControlFlow.java index 309f4f5b..076a29eb 100644 --- a/src/main/java/carpet/script/language/ControlFlow.java +++ b/src/main/java/carpet/script/language/ControlFlow.java @@ -49,13 +49,15 @@ public class ControlFlow { switch (lv.size()) { case 0: - throw new ThrowStatement("No further information", Value.NULL); + throw new ThrowStatement(new StringValue("No further information"), Value.NULL, Value.NULL); case 1: throw new ThrowStatement(lv.get(0).evalValue(c)); case 2: - throw new ThrowStatement(lv.get(1).evalValue(c).getString(), lv.get(0).evalValue(c)); + throw new ThrowStatement(lv.get(1).evalValue(c), lv.get(0).evalValue(c), Value.NULL); + case 3: + throw new ThrowStatement(lv.get(1).evalValue(c), lv.get(0).evalValue(c), lv.get(2).evalValue(c)); default: - throw new InternalExpressionException("throw() can't accept more than 2 parameters"); + throw new InternalExpressionException("throw() can't accept more than 3 parameters"); } }); @@ -70,17 +72,23 @@ public class ControlFlow { } catch (ProcessedThrowStatement ret) { + if (ret.exception.isError()) + throw ret; if (lv.size() == 1) return (c_, t_) -> Value.NULL; LazyValue __ = c.getVariable("_"); - c.setVariable("_", (__c, __t) -> ret.exceptionName.reboundedTo("_")); + c.setVariable("_", (__c, __t) -> ret.exception.getValue().reboundedTo("_")); LazyValue __msg = c.getVariable("_msg"); c.setVariable("_msg", (__c, __t) -> StringValue.of(ret.message).reboundedTo("_msg")); Value val; if (lv.size() == 3) { - if (!ret.exceptionName.equals(lv.get(1).evalValue(c))) + if (!ret.exception.isInstance(lv.get(1).evalValue(c))) + { + c.setVariable("_", __); + c.setVariable("_msg", __msg); throw ret; + } val = lv.get(2).evalValue(c, t); } else diff --git a/src/main/java/carpet/script/utils/ShapeDispatcher.java b/src/main/java/carpet/script/utils/ShapeDispatcher.java index ac24c46f..84aab23a 100644 --- a/src/main/java/carpet/script/utils/ShapeDispatcher.java +++ b/src/main/java/carpet/script/utils/ShapeDispatcher.java @@ -5,6 +5,7 @@ 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; @@ -157,7 +158,7 @@ public class ShapeDispatcher } catch (CommandSyntaxException e) { - throw new ThrowStatement("No such particle: "+name, ThrowStatement.UNKNOWN_PARTICLE); + throw new ThrowStatement("No such particle: "+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 56846d9b..fe26f57f 100644 --- a/src/main/java/carpet/script/value/BlockValue.java +++ b/src/main/java/carpet/script/value/BlockValue.java @@ -3,6 +3,7 @@ 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; @@ -75,7 +76,7 @@ public class BlockValue extends Value catch (CommandSyntaxException ignored) { } - throw new ThrowStatement("Cannot parse block: "+str, ThrowStatement.UNKNOWN_BLOCK); + throw new ThrowStatement("Cannot parse block: "+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/NBTSerializableValue.java b/src/main/java/carpet/script/value/NBTSerializableValue.java index 1a790c2b..11595d06 100644 --- a/src/main/java/carpet/script/value/NBTSerializableValue.java +++ b/src/main/java/carpet/script/value/NBTSerializableValue.java @@ -5,6 +5,7 @@ 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; @@ -298,7 +299,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa } catch (CommandSyntaxException e) { - throw new ThrowStatement("Incorrect item: "+itemString, ThrowStatement.UNKNOWN_ITEM); + throw new ThrowStatement("Incorrect item: "+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 9fc4e62f..320916ae 100644 --- a/src/main/java/carpet/script/value/ValueConversions.java +++ b/src/main/java/carpet/script/value/ValueConversions.java @@ -3,6 +3,7 @@ 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.MaterialColor; @@ -159,7 +160,7 @@ public class ValueConversions } } if (dim == null) - throw new ThrowStatement("Incorrect dimension string: "+dimString, ThrowStatement.UNKNOWN_DIMENSION); + throw new ThrowStatement("Incorrect dimension string: "+dimString, Throwables.UNKNOWN_DIMENSION); return server.getWorld(dim); } } From 0b86ac1a8704311fb38573f8356584796e5317c4 Mon Sep 17 00:00:00 2001 From: altrisi Date: Sun, 21 Feb 2021 01:52:55 +0100 Subject: [PATCH 03/16] Add multi-catch to try block --- .../language/FunctionsAndControlFlow.md | 10 ++++--- .../carpet/script/language/ControlFlow.java | 27 ++++++++++++++----- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/docs/scarpet/language/FunctionsAndControlFlow.md b/docs/scarpet/language/FunctionsAndControlFlow.md index 28a03fb0..8b4d50a3 100644 --- a/docs/scarpet/language/FunctionsAndControlFlow.md +++ b/docs/scarpet/language/FunctionsAndControlFlow.md @@ -234,13 +234,17 @@ 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, id_filter?, catch_expr(_, _msg)?)` +### `try(expr, id_filter?, catch_expr(_, _msg)?, id_filter2?, catch_expr2(_, _msg), ...)` `try` function evaluates expression, and continues further unless an exception is thrown anywhere inside `expr`, it being produced by `throw` or a catchable exception being thrown by Carpet. In that case the `catch_expr` is evaluated with `_` set to the id of the exception and `_msg` set to the exception message. If an `id_filter` is present, only exceptions -with an id equal to that value will be caught, and any other exception will continue up the stack. 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. +with an id equal to that value will be caught, and any other exception will continue up the stack. You can specify multiple filters +and their respective `catch_expr` to be able to catch different exceptions within the same code. When an exception occurs, the script +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. 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 mechanic accepts skipping catch expression - then try returns `null`. This mechanism allows to terminate large portion of a convoluted call stack and continue program execution. diff --git a/src/main/java/carpet/script/language/ControlFlow.java b/src/main/java/carpet/script/language/ControlFlow.java index 076a29eb..cbf5bebd 100644 --- a/src/main/java/carpet/script/language/ControlFlow.java +++ b/src/main/java/carpet/script/language/ControlFlow.java @@ -76,20 +76,34 @@ public class ControlFlow { throw ret; if (lv.size() == 1) return (c_, t_) -> Value.NULL; + // This is outside since it is also used in try(expr,catch_expr). LazyValue __ = c.getVariable("_"); c.setVariable("_", (__c, __t) -> ret.exception.getValue().reboundedTo("_")); LazyValue __msg = c.getVariable("_msg"); c.setVariable("_msg", (__c, __t) -> StringValue.of(ret.message).reboundedTo("_msg")); - Value val; - if (lv.size() == 3) + Value val = null; // This is always assigned, just the compiler doesn't know + if (lv.size() >= 3) { - if (!ret.exception.isInstance(lv.get(1).evalValue(c))) + if (lv.size() % 2 == 0) + throw new InternalExpressionException("Multi-catch block needs a catch expression for every filter"); + int pointer = 1; + boolean handled = false; + while (pointer < lv.size() -1) { - c.setVariable("_", __); + if (ret.exception.isInstance(lv.get(pointer).evalValue(c))) + { + val = lv.get(pointer + 1).evalValue(c, t); + handled = true; + break; + } + pointer += 2; + } + if (!handled) + { + c.setVariable("_", __); c.setVariable("_msg", __msg); throw ret; } - val = lv.get(2).evalValue(c, t); } else { @@ -97,7 +111,8 @@ public class ControlFlow { } c.setVariable("_",__); c.setVariable("_msg", __msg); - return (c_, t_) -> val; + Value retval = val; + return (c_, t_) -> retval; } }); } From 516de37dd294cf6dcee2c36a98bee92821617b59 Mon Sep 17 00:00:00 2001 From: altrisi Date: Sun, 21 Feb 2021 02:16:03 +0100 Subject: [PATCH 04/16] Deprecate try without id and therefore throw without id --- .../language/FunctionsAndControlFlow.md | 23 +++++++++++-------- .../carpet/script/language/ControlFlow.java | 11 ++++++--- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/docs/scarpet/language/FunctionsAndControlFlow.md b/docs/scarpet/language/FunctionsAndControlFlow.md index 8b4d50a3..4562b4dd 100644 --- a/docs/scarpet/language/FunctionsAndControlFlow.md +++ b/docs/scarpet/language/FunctionsAndControlFlow.md @@ -234,27 +234,28 @@ 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, id_filter?, catch_expr(_, _msg)?, id_filter2?, catch_expr2(_, _msg), ...)` +### `try(expr, id_filter, catch_expr(_, _msg), id_filter2?, catch_expr2(_, _msg), ...)` `try` function evaluates expression, and continues further unless an exception is thrown anywhere inside `expr`, it being produced by `throw` or a catchable exception being thrown by Carpet. In that case the `catch_expr` is evaluated with -`_` set to the id of the exception and `_msg` set to the exception message. If an `id_filter` is present, only exceptions -with an id equal to that value will be caught, and any other exception will continue up the stack. You can specify multiple filters +`_` 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 +to a provided `id_filter` will be caught, and any other exception will continue up the stack. You can specify multiple filters and their respective `catch_expr` to be able to catch different exceptions within the same code. When an exception occurs, the script 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. 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 mechanic accepts skipping catch expression - then try returns `null`. This mechanism allows to terminate large -portion of a convoluted call stack and continue program execution. - -A `try` block will not only catch any exception whose id match the `id_filter`, but also any exception that has a parent with -their id being that one, recursively. +This mechanism allows to terminate large portion of a convoluted call stack and continue program execution. 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: +- `try(expr)`: Filter the exception you are looking for, and provide something as the `catch_expr` (use `null` if you have nothing to provide). + 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 hierarchy is the following: - `exception`: This is the base exception. Every exception has this as its top-level parent @@ -274,7 +275,7 @@ hierarchy is the following: - `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?)` +### `throw(id, message?, parent?)` Throws an exception that can be caught in a `try` block (see above). If ran without arguments, it will pass `null` as the value to the `catch_expr`. `message` is the message to show to chat or console in case the `throw` is @@ -283,7 +284,9 @@ unhandled. By default, the parent of any exception thrown by this function is `user_defined_exception`. 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 -`exception` > `user_defined_exception` > `parent` > `id` +`exception` > `user_defined_exception` > `parent` > `id`. + +Deprecated usage: `throw()` (without id): Specify at least the id of your exception ### `if(cond, expr, cond?, expr?, ..., default?)` diff --git a/src/main/java/carpet/script/language/ControlFlow.java b/src/main/java/carpet/script/language/ControlFlow.java index cbf5bebd..029bf8ac 100644 --- a/src/main/java/carpet/script/language/ControlFlow.java +++ b/src/main/java/carpet/script/language/ControlFlow.java @@ -49,6 +49,7 @@ public class ControlFlow { switch (lv.size()) { case 0: + c.host.issueDeprecation("throw() (without id)"); throw new ThrowStatement(new StringValue("No further information"), Value.NULL, Value.NULL); case 1: throw new ThrowStatement(lv.get(0).evalValue(c)); @@ -64,7 +65,7 @@ public class ControlFlow { 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, a filter, and a catch_epr"); try { Value retval = lv.get(0).evalValue(c, t); @@ -75,13 +76,16 @@ public class ControlFlow { if (ret.exception.isError()) throw ret; if (lv.size() == 1) + { + c.host.issueDeprecation("try(expr) (without filter and catch expression)"); return (c_, t_) -> Value.NULL; - // This is outside since it is also used in try(expr,catch_expr). + } + // This is outside since it is used in try(expr,catch_expr) LazyValue __ = c.getVariable("_"); c.setVariable("_", (__c, __t) -> ret.exception.getValue().reboundedTo("_")); LazyValue __msg = c.getVariable("_msg"); c.setVariable("_msg", (__c, __t) -> StringValue.of(ret.message).reboundedTo("_msg")); - Value val = null; // This is always assigned, just the compiler doesn't know + Value val = null; // This is always assigned at some point, just the compiler doesn't know if (lv.size() >= 3) { if (lv.size() % 2 == 0) @@ -107,6 +111,7 @@ public class ControlFlow { } else { + c.host.issueDeprecation("try(expr, catch_expr) (without filter)"); val = lv.get(1).evalValue(c, t); } c.setVariable("_",__); From ae70529f2041dd6c0c6f9831450acafd91cad3c8 Mon Sep 17 00:00:00 2001 From: altrisi Date: Sun, 21 Feb 2021 02:50:49 +0100 Subject: [PATCH 05/16] Mention error in docs --- docs/scarpet/language/FunctionsAndControlFlow.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/scarpet/language/FunctionsAndControlFlow.md b/docs/scarpet/language/FunctionsAndControlFlow.md index 4562b4dd..79c931a3 100644 --- a/docs/scarpet/language/FunctionsAndControlFlow.md +++ b/docs/scarpet/language/FunctionsAndControlFlow.md @@ -286,6 +286,8 @@ You can specify the `parent` to be either an already existing exception from the will not inherit from `user_defined_exception`, or specify a custom one, in which case your exception will inherit from `exception` > `user_defined_exception` > `parent` > `id`. +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. + Deprecated usage: `throw()` (without id): Specify at least the id of your exception ### `if(cond, expr, cond?, expr?, ..., default?)` From 7b1c58d81e9d2c9738b26d06204c38b1174ca39e Mon Sep 17 00:00:00 2001 From: altrisi Date: Sun, 21 Feb 2021 12:34:44 +0100 Subject: [PATCH 06/16] Minor docs and visibility changes for exceptions --- .../carpet/script/exception/Throwables.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/carpet/script/exception/Throwables.java b/src/main/java/carpet/script/exception/Throwables.java index 4bdfeebe..ab6af103 100644 --- a/src/main/java/carpet/script/exception/Throwables.java +++ b/src/main/java/carpet/script/exception/Throwables.java @@ -12,10 +12,10 @@ import carpet.script.value.Value; * methods to check whether filters are compatible with those. * * Exceptions here cannot be thrown as an actual {@link Throwable}, they are just part of the metadata for - * {@link ThrowStatement} + * {@link ThrowStatement} and {@link ProcessedThrowStatement} */ public class Throwables { - private static final Map byValue = new HashMap<>(); + protected static final Map byValue = new HashMap<>(); public static final Exception EXCEPTION = register("exception", null); public static final Exception VALUE_EXCEPTION = register("value_exception", EXCEPTION); public static final Exception UNKNOWN_ITEM = register("unknown_item", VALUE_EXCEPTION); @@ -39,11 +39,11 @@ public class Throwables { * Creates an exception and registers it to be used as parent for * user defined exceptions in Scarpet's throw function. *

Scarpet exceptions should, in general, have a top-level parent being {@link Throwables#EXCEPTION} - * @param name The name for the exception. Will be converted into a {@link StringValue} + * @param value The value for the exception as a {@link String}. Will be converted into a {@link StringValue} * @param parent The parent of the exception being created, or null if top-level * @return The created exception */ - protected static Exception register(String value, Exception parent) + public static Exception register(String value, Exception parent) { Exception exc = new Exception(value, parent); byValue.put(exc.getValue(), exc); @@ -65,14 +65,14 @@ public class Throwables { *

Parent will default to {@link Throwables#USER_DEFINED} if provided * parentValue meets Value#isNull, else it will create a new {@link Exception}, * with its parent being {@link Throwables#USER_DEFINED}. - * @param name The name/id of the exception - * @param parentValue An optional Value matching the name of a parent exception. - *
Accepts a {@link NullValue}, but not a null + * @param value The {@link Value} of the exception + * @param parentValue An optional {@link Value} matching the name of a parent exception. + *
Accepts a {@link NullValue}, but not a {@code null} */ public Exception(Value value, Value parentValue) { if (byValue.containsKey(value)) - throw new InternalExpressionException("Exception id can't be the same as existing exception. Use it as parent instead"); + throw new InternalExpressionException("Exception value can't be the same as existing exception. Use it as parent instead"); this.value = value; if (!parentValue.isNull()) { @@ -87,7 +87,7 @@ public class Throwables { * Creates a new exception. *

Not suitable for creating exceptions that can't be caught. * Use an {@link InternalExpressionException} for that - * @param name The exception's name/id + * @param value The exception's value as a {@link String} * @param parent The parent exception, or null if no parent is available */ protected Exception(String value, Exception parent) @@ -100,7 +100,7 @@ public class Throwables { /** * Checks whether the given filter matches an instance of this exception, including checking equality * with itself and possible parents. - * @param filter The value to check against + * @param filter The {@link Value} to check against * @return Whether or not the given value matches this exception's hierarchy */ public boolean isInstance(Value filter) { From 44101ddd3d60f640a4608a9bcb66dd60dc9c2035 Mon Sep 17 00:00:00 2001 From: altrisi Date: Tue, 2 Mar 2021 14:26:05 +0100 Subject: [PATCH 07/16] Undeprecate and filter to user-defined --- .../carpet/script/language/ControlFlow.java | 51 +++++++++++-------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/src/main/java/carpet/script/language/ControlFlow.java b/src/main/java/carpet/script/language/ControlFlow.java index 029bf8ac..b704a908 100644 --- a/src/main/java/carpet/script/language/ControlFlow.java +++ b/src/main/java/carpet/script/language/ControlFlow.java @@ -7,6 +7,7 @@ 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.StringValue; import carpet.script.value.Value; @@ -49,7 +50,6 @@ public class ControlFlow { switch (lv.size()) { case 0: - c.host.issueDeprecation("throw() (without id)"); throw new ThrowStatement(new StringValue("No further information"), Value.NULL, Value.NULL); case 1: throw new ThrowStatement(lv.get(0).evalValue(c)); @@ -65,7 +65,7 @@ public class ControlFlow { expression.addLazyFunction("try", -1, (c, t, lv) -> { if (lv.size()==0) - throw new InternalExpressionException("'try' needs at least an expression block, a filter, and a catch_epr"); + 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); @@ -77,21 +77,36 @@ public class ControlFlow { throw ret; if (lv.size() == 1) { - c.host.issueDeprecation("try(expr) (without filter and catch expression)"); + if (!ret.exception.isInstance(Throwables.USER_DEFINED.getValue())) + throw ret; return (c_, t_) -> Value.NULL; } - // This is outside since it is used in try(expr,catch_expr) + if (lv.size() == 0 || lv.size() % 2 == 0) + { + throw new InternalExpressionException("Try-catch block needs the code to run, and either a catch_expr or a number of pairs of filters and catch_expr"); + } + + Value val = null; // This is always assigned at some point, just the compiler doesn't know + LazyValue __ = c.getVariable("_"); c.setVariable("_", (__c, __t) -> ret.exception.getValue().reboundedTo("_")); LazyValue __msg = c.getVariable("_msg"); c.setVariable("_msg", (__c, __t) -> StringValue.of(ret.message).reboundedTo("_msg")); - Value val = null; // This is always assigned at some point, just the compiler doesn't know - if (lv.size() >= 3) + + boolean handled; + if (lv.size() == 2) + { + if (!ret.exception.isInstance(Throwables.USER_DEFINED.getValue())) + handled = false; + else { + val = lv.get(1).evalValue(c, t); + handled = true; + } + } + else { - if (lv.size() % 2 == 0) - throw new InternalExpressionException("Multi-catch block needs a catch expression for every filter"); int pointer = 1; - boolean handled = false; + handled = false; while (pointer < lv.size() -1) { if (ret.exception.isInstance(lv.get(pointer).evalValue(c))) @@ -102,20 +117,14 @@ public class ControlFlow { } pointer += 2; } - if (!handled) - { - c.setVariable("_", __); - c.setVariable("_msg", __msg); - throw ret; - } } - else - { - c.host.issueDeprecation("try(expr, catch_expr) (without filter)"); - val = lv.get(1).evalValue(c, t); - } - c.setVariable("_",__); + c.setVariable("_", __); c.setVariable("_msg", __msg); + + if (!handled) + { + throw ret; + } Value retval = val; return (c_, t_) -> retval; } From b123899abd41318bb0b8613e86ec432e48ad65be Mon Sep 17 00:00:00 2001 From: altrisi Date: Sun, 7 Mar 2021 22:14:35 +0100 Subject: [PATCH 08/16] Fix `run()` with a locked SettingsManager (#755) * Fix run() in locked SettingsManager * Fix small security issue while I'm at it * Notify command change --- src/main/java/carpet/CarpetSettings.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/java/carpet/CarpetSettings.java b/src/main/java/carpet/CarpetSettings.java index 6a1ab764..1435dc56 100644 --- a/src/main/java/carpet/CarpetSettings.java +++ b/src/main/java/carpet/CarpetSettings.java @@ -413,20 +413,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"; From 8a1c9f2d0b451377fc5e4deea09472feaaa37db3 Mon Sep 17 00:00:00 2001 From: altrisi Date: Sun, 7 Mar 2021 22:16:20 +0100 Subject: [PATCH 09/16] Fix symbolic links (#754) --- .../java/carpet/script/CarpetScriptServer.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) 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))); } From 36eccbc2df0f9880724ecc4a8267cb9f0e2d8416 Mon Sep 17 00:00:00 2001 From: altrisi Date: Mon, 8 Mar 2021 00:09:02 +0100 Subject: [PATCH 10/16] Fix javadoc --- src/main/java/carpet/script/exception/ThrowStatement.java | 7 ++++--- src/main/java/carpet/script/exception/Throwables.java | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/carpet/script/exception/ThrowStatement.java b/src/main/java/carpet/script/exception/ThrowStatement.java index b8cb4155..527befe1 100644 --- a/src/main/java/carpet/script/exception/ThrowStatement.java +++ b/src/main/java/carpet/script/exception/ThrowStatement.java @@ -39,9 +39,10 @@ public class ThrowStatement extends InternalExpressionException * 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 value A String that will be converted - * to a value to pass to {@code catch} - * blocks + * @param exception An {@link Exception} containing the inheritance data + * for this exception. When throwing from Java, + * those exceptions should be pre-registered and used + * instead of creating new ones each time. * @param parent This exception's data */ public ThrowStatement(String message, Exception exception) diff --git a/src/main/java/carpet/script/exception/Throwables.java b/src/main/java/carpet/script/exception/Throwables.java index ab6af103..ba42f993 100644 --- a/src/main/java/carpet/script/exception/Throwables.java +++ b/src/main/java/carpet/script/exception/Throwables.java @@ -65,6 +65,8 @@ public class Throwables { *

Parent will default to {@link Throwables#USER_DEFINED} if provided * parentValue meets Value#isNull, else it will create a new {@link Exception}, * with its parent being {@link Throwables#USER_DEFINED}. + *

If passed parent matches {@link Throwables#ERROR}, it will create an error + * exception, which cannot be caught. * @param value The {@link Value} of the exception * @param parentValue An optional {@link Value} matching the name of a parent exception. *
Accepts a {@link NullValue}, but not a {@code null} @@ -98,7 +100,7 @@ public class Throwables { } /** - * Checks whether the given filter matches an instance of this exception, including checking equality + * Checks whether the given filter matches an instance of this exception, by checking equality * with itself and possible parents. * @param filter The {@link Value} to check against * @return Whether or not the given value matches this exception's hierarchy From d48f78ff7b87c419935cb8a83c297d3d80da693d Mon Sep 17 00:00:00 2001 From: gnembon Date: Mon, 8 Mar 2021 00:44:40 -0400 Subject: [PATCH 11/16] simplfy --- .../script/exception/ExpressionException.java | 8 + .../exception/ProcessedThrowStatement.java | 13 +- .../script/exception/ThrowStatement.java | 41 ++-- .../carpet/script/exception/Throwables.java | 183 ++++++++---------- .../carpet/script/language/ControlFlow.java | 39 ++-- 5 files changed, 118 insertions(+), 166 deletions(-) diff --git a/src/main/java/carpet/script/exception/ExpressionException.java b/src/main/java/carpet/script/exception/ExpressionException.java index ec72fccb..ab9ec2e9 100644 --- a/src/main/java/carpet/script/exception/ExpressionException.java +++ b/src/main/java/carpet/script/exception/ExpressionException.java @@ -37,6 +37,14 @@ public class ExpressionException extends RuntimeException implements ResolvedExc context = c; } + public ExpressionException(Context c, Expression e, Tokenizer.Token t, Supplier messageSupplier, List stack) + { + super("Error"); + this.stack.addAll(stack); + lazyStacktrace = messageSupplier; + context = c; + } + private static final Fluff.TriFunction> errorMaker = (expr, /*Nullable*/ token, errmessage) -> { diff --git a/src/main/java/carpet/script/exception/ProcessedThrowStatement.java b/src/main/java/carpet/script/exception/ProcessedThrowStatement.java index 003b71b0..977cf51f 100644 --- a/src/main/java/carpet/script/exception/ProcessedThrowStatement.java +++ b/src/main/java/carpet/script/exception/ProcessedThrowStatement.java @@ -6,14 +6,15 @@ 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.Exception exception; - public final String message; + public final Throwables thrownExceptionType; + public final Value data; - public ProcessedThrowStatement(Context c, Expression e, Token token, String message, List stack, Throwables.Exception exception) { - super(c, e, token, "Unhandled exception: "+message, stack); - this.exception = exception; - this.message = message; + 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 527befe1..01bf9ccf 100644 --- a/src/main/java/carpet/script/exception/ThrowStatement.java +++ b/src/main/java/carpet/script/exception/ThrowStatement.java @@ -3,35 +3,25 @@ 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; -import static carpet.script.exception.Throwables.Exception; - public class ThrowStatement extends InternalExpressionException { - private final Exception exception; - /** - * Creates a throw exception from a value. - * That value will also be used as the message.
- * To be used when throwing from Scarpet's {@code throw} function with a single argument - * @param value The value to pass - */ - public ThrowStatement(Value value) - { - this(value, value, Value.NULL); - } + private final Throwables thrownExceptionType; + private final Value exceptionData; /** * 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 message The message to display if uncaught - * @param value The value to pass - * @param parent A parent's name + * @param data The value to pass + * @param type Exception type */ - public ThrowStatement(Value message, Value value, Value parent) + public ThrowStatement(Value data, Throwables type) { - super(message.getString()); - exception = new Exception(value, parent); + super(type.getId()); + exceptionData = data; + thrownExceptionType = type; } /** @@ -39,20 +29,19 @@ public class ThrowStatement extends InternalExpressionException * 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 exception An {@link Exception} containing the inheritance data + * @param thrownExceptionType An {@link Throwables} containing the inheritance data * for this exception. When throwing from Java, - * those exceptions should be pre-registered and used - * instead of creating new ones each time. - * @param parent This exception's data + * those exceptions should be pre-registered. */ - public ThrowStatement(String message, Exception exception) + public ThrowStatement(String message, Throwables thrownExceptionType) { super(message); - this.exception = exception; + this.exceptionData = StringValue.of(message); + this.thrownExceptionType = thrownExceptionType; } @Override public ExpressionException promote(Context c, Expression e, Token token) { - return new ProcessedThrowStatement(c, e, token, getMessage(), stack, exception); + return new ProcessedThrowStatement(c, e, token, stack, thrownExceptionType, exceptionData); } } diff --git a/src/main/java/carpet/script/exception/Throwables.java b/src/main/java/carpet/script/exception/Throwables.java index ba42f993..136aa09e 100644 --- a/src/main/java/carpet/script/exception/Throwables.java +++ b/src/main/java/carpet/script/exception/Throwables.java @@ -1,129 +1,98 @@ package carpet.script.exception; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; - -import carpet.script.value.NullValue; -import carpet.script.value.StringValue; -import carpet.script.value.Value; +import java.util.Set; /** - * This class contains default Scarpet catchable exception's ids, as well as their inheritance and + * This class contains default Scarpet catchable types, as well as their inheritance and * methods to check whether filters are compatible with those. - * - * Exceptions here cannot be thrown as an actual {@link Throwable}, they are just part of the metadata for - * {@link ThrowStatement} and {@link ProcessedThrowStatement} */ public class Throwables { - protected static final Map byValue = new HashMap<>(); - public static final Exception EXCEPTION = register("exception", null); - public static final Exception VALUE_EXCEPTION = register("value_exception", EXCEPTION); - public static final Exception UNKNOWN_ITEM = register("unknown_item", VALUE_EXCEPTION); - public static final Exception UNKNOWN_BLOCK = register("unknown_block", VALUE_EXCEPTION); - public static final Exception UNKNOWN_BIOME = register("unknown_biome", VALUE_EXCEPTION); - public static final Exception UNKNOWN_SOUND = register("unknown_sound", VALUE_EXCEPTION); - public static final Exception UNKNOWN_PARTICLE = register("unknown_particle", VALUE_EXCEPTION); - public static final Exception UNKNOWN_POI_TYPE = register("unknown_poi_type", VALUE_EXCEPTION); - public static final Exception UNKNOWN_DIMENSION = register("unknown_dimension", VALUE_EXCEPTION); - public static final Exception UNKNOWN_STRUCTURE = register("unknown_structure", VALUE_EXCEPTION); - public static final Exception UNKNOWN_CRITERION = register("unknown_criterion", VALUE_EXCEPTION); - public static final Exception FILE_READ_EXCEPTION = register("file_read_exception", EXCEPTION); - public static final Exception NBT_READ = register("nbt_read_exception", FILE_READ_EXCEPTION); - public static final Exception JSON_READ = register("json_read_exception", FILE_READ_EXCEPTION); - public static final Exception USER_DEFINED = register("user_defined_exception", EXCEPTION); - protected static final Exception ERROR = register("error", null); - - private Throwables() {} - + private final String id; + private final Throwables parent; + + private static final Map byValue = new HashMap<>(); + private static final Map> handles = 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_TYPE = register("unknown_poi_type", 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 FILE_READ_EXCEPTION = register("file_read_exception", THROWN_EXCEPTION_TYPE); + public static final Throwables NBT_READ = register("nbt_read_exception", FILE_READ_EXCEPTION); + public static final Throwables JSON_READ = register("json_read_exception", FILE_READ_EXCEPTION); + public static final Throwables USER_DEFINED = register("user_defined_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, in general, have a top-level parent being {@link Throwables#EXCEPTION} - * @param value The value for the exception as a {@link String}. Will be converted into a {@link StringValue} + *

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 Exception register(String value, Exception parent) + public static Throwables register(String id, Throwables parent) { - Exception exc = new Exception(value, parent); - byValue.put(exc.getValue(), exc); + Throwables exc = new Throwables(id, parent); + byValue.put(id, exc); + Set itHandles = new HashSet<>(); + itHandles.add(exc); + while (parent!=null) + { + handles.get(parent.id).add(exc); + parent = parent.parent; + } + handles.put(id, itHandles); return exc; } - + /** - * The data of a {@link ThrowStatement} exception. Supports creating with inheritance and - * checking whether a filter is compatible with them. + * 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 static class Exception { - private final Value value; - private final Exception parent; - private final boolean isError; - - /** - * Creates a new exception, with an optional parent. - *

To be used in Scarpet's throw command - *

Parent will default to {@link Throwables#USER_DEFINED} if provided - * parentValue meets Value#isNull, else it will create a new {@link Exception}, - * with its parent being {@link Throwables#USER_DEFINED}. - *

If passed parent matches {@link Throwables#ERROR}, it will create an error - * exception, which cannot be caught. - * @param value The {@link Value} of the exception - * @param parentValue An optional {@link Value} matching the name of a parent exception. - *
Accepts a {@link NullValue}, but not a {@code null} - */ - public Exception(Value value, Value parentValue) - { - if (byValue.containsKey(value)) - throw new InternalExpressionException("Exception value can't be the same as existing exception. Use it as parent instead"); - this.value = value; - if (!parentValue.isNull()) - { - Exception possibleParent = byValue.get(parentValue); - parent = possibleParent == null ? new Exception(parentValue, Value.NULL) : possibleParent; - } else - parent = USER_DEFINED; - isError = this.parent == ERROR; - } - - /** - * Creates a new exception. - *

Not suitable for creating exceptions that can't be caught. - * Use an {@link InternalExpressionException} for that - * @param value The exception's value as a {@link String} - * @param parent The parent exception, or null if no parent is available - */ - protected Exception(String value, Exception parent) - { - this.value = StringValue.of(value); - this.parent = parent; - this.isError = false; - } - - /** - * Checks whether the given filter matches an instance of this exception, by checking equality - * with itself and possible parents. - * @param filter The {@link Value} to check against - * @return Whether or not the given value matches this exception's hierarchy - */ - public boolean isInstance(Value filter) { - return getValue().equals(filter) || (parent != null && parent.isInstance(filter)); - } - - /** - * Returns the {@link Value} provided when creating this exception - * @return The {@link Value} of this exception - */ - public Value getValue() { - return value; - } - - /** - * Returns whether this exception has been defined as an error. - *

Error exceptions cannot be caught in Scarpet - * @return Whether this exception has been defined as an error - */ - public boolean isError() { - return isError; - } + protected Throwables(String id, Throwables parent) + { + this.id = id; + this.parent = parent; + } + + public static Throwables getTypeForException(String type) + { + Throwables properType = byValue.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) + { + Set relevantSet = handles.get(filter); + if (relevantSet == null) throw new InternalExpressionException("Unknown exception type: "+filter); + return relevantSet.contains(this); + } + + /** + * 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 b704a908..f5173f26 100644 --- a/src/main/java/carpet/script/language/ControlFlow.java +++ b/src/main/java/carpet/script/language/ControlFlow.java @@ -8,7 +8,6 @@ import carpet.script.exception.InternalExpressionException; import carpet.script.exception.ProcessedThrowStatement; import carpet.script.exception.ThrowStatement; import carpet.script.exception.Throwables; -import carpet.script.value.StringValue; import carpet.script.value.Value; public class ControlFlow { @@ -50,15 +49,13 @@ public class ControlFlow { switch (lv.size()) { case 0: - throw new ThrowStatement(new StringValue("No further information"), Value.NULL, Value.NULL); + throw new ThrowStatement(Value.NULL, Throwables.USER_DEFINED); case 1: - throw new ThrowStatement(lv.get(0).evalValue(c)); + throw new ThrowStatement(lv.get(0).evalValue(c), Throwables.USER_DEFINED ); case 2: - throw new ThrowStatement(lv.get(1).evalValue(c), lv.get(0).evalValue(c), Value.NULL); - case 3: - throw new ThrowStatement(lv.get(1).evalValue(c), lv.get(0).evalValue(c), lv.get(2).evalValue(c)); + throw new ThrowStatement(lv.get(1).evalValue(c), Throwables.getTypeForException(lv.get(0).evalValue(c).getString())); default: - throw new InternalExpressionException("throw() can't accept more than 3 parameters"); + throw new InternalExpressionException("throw() can't accept more than 2 parameters"); } }); @@ -73,55 +70,43 @@ public class ControlFlow { } catch (ProcessedThrowStatement ret) { - if (ret.exception.isError()) - throw ret; if (lv.size() == 1) { - if (!ret.exception.isInstance(Throwables.USER_DEFINED.getValue())) + if (ret.thrownExceptionType != Throwables.USER_DEFINED) throw ret; return (c_, t_) -> Value.NULL; } - if (lv.size() == 0 || lv.size() % 2 == 0) + if (lv.size() > 3 && lv.size() % 2 == 0) { - throw new InternalExpressionException("Try-catch block needs the code to run, and either a catch_expr or a number of pairs of filters and catch_expr"); + 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.exception.getValue().reboundedTo("_")); - LazyValue __msg = c.getVariable("_msg"); - c.setVariable("_msg", (__c, __t) -> StringValue.of(ret.message).reboundedTo("_msg")); - - boolean handled; + c.setVariable("_", (__c, __t) -> ret.data.reboundedTo("_")); + if (lv.size() == 2) { - if (!ret.exception.isInstance(Throwables.USER_DEFINED.getValue())) - handled = false; - else { + if (ret.thrownExceptionType == Throwables.USER_DEFINED) val = lv.get(1).evalValue(c, t); - handled = true; - } } else { int pointer = 1; - handled = false; while (pointer < lv.size() -1) { - if (ret.exception.isInstance(lv.get(pointer).evalValue(c))) + if (ret.thrownExceptionType.isRelevantFor(lv.get(pointer).evalValue(c).getString())) { val = lv.get(pointer + 1).evalValue(c, t); - handled = true; break; } pointer += 2; } } c.setVariable("_", __); - c.setVariable("_msg", __msg); - if (!handled) + if (val == null) // not handled { throw ret; } From d72a617290235d6dd321816214ba6f8ed33a271b Mon Sep 17 00:00:00 2001 From: gnembon Date: Tue, 9 Mar 2021 04:57:23 -0400 Subject: [PATCH 12/16] added trace information to thrown exceptions and clean some of the docs --- .../language/FunctionsAndControlFlow.md | 104 ++++++++++-------- src/main/java/carpet/script/Tokenizer.java | 1 + .../script/exception/ExpressionException.java | 10 +- .../carpet/script/exception/Throwables.java | 32 +++--- .../carpet/script/language/ControlFlow.java | 31 +++++- 5 files changed, 111 insertions(+), 67 deletions(-) diff --git a/docs/scarpet/language/FunctionsAndControlFlow.md b/docs/scarpet/language/FunctionsAndControlFlow.md index 79c931a3..73d5d1fc 100644 --- a/docs/scarpet/language/FunctionsAndControlFlow.md +++ b/docs/scarpet/language/FunctionsAndControlFlow.md @@ -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. -### `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`, -it being produced by `throw` or a catchable exception being thrown by Carpet. In that case the `catch_expr` is evaluated with -`_` 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 -to a provided `id_filter` will be caught, and any other exception will continue up the stack. You can specify multiple filters -and their respective `catch_expr` to be able to catch different exceptions within the same code. When an exception occurs, the script -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. 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. +`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. -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. +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. -Deprecated usages: -- `try(expr)`: Filter the exception you are looking for, and provide something as the `catch_expr` (use `null` if you have nothing to provide). - Example replacement: `try(expr, 'exception', null)` -- `try(expr, catch_expr)`: Filter the exception you are looking for. +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. -In this documentation, those are documented in at least most of the functions that throw them, and the current exception -hierarchy is the following: -- `exception`: This is the base exception. Every exception has this as its top-level parent - - `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 +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
+   )
+);
 
-### `throw(id, message?, parent?)`
+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]} +``` -Throws an exception that can be caught in a `try` block (see above). If ran without arguments, it will pass `null` -as the value to the `catch_expr`. `message` is the message to show to chat or console in case the `throw` is -unhandled. +### `throw(value?)` `throw(type, value)` -By default, the parent of any exception thrown by this function is `user_defined_exception`. -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 -`exception` > `user_defined_exception` > `parent` > `id`. - -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. - -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?)` 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/exception/ExpressionException.java b/src/main/java/carpet/script/exception/ExpressionException.java index ab9ec2e9..c62e3db7 100644 --- a/src/main/java/carpet/script/exception/ExpressionException.java +++ b/src/main/java/carpet/script/exception/ExpressionException.java @@ -14,26 +14,29 @@ import java.util.function.Supplier; 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 lazyStacktrace; private String cachedMessage = null; 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) { super("Error"); lazyStacktrace = () -> makeMessage(c, e, t, message); + token = t; context = c; } public ExpressionException(Context c, Expression e, Tokenizer.Token t, String message, List stack) { super("Error"); this.stack.addAll(stack); - lazyStacktrace = () -> makeMessage(c, e, t, message); + lazyStacktrace = () -> makeMessage(c, e, t, message); + token = t; context = c; } @@ -41,7 +44,8 @@ public class ExpressionException extends RuntimeException implements ResolvedExc { super("Error"); this.stack.addAll(stack); - lazyStacktrace = messageSupplier; + lazyStacktrace = () -> makeMessage(c, e, t, messageSupplier.get()); + token = t; context = c; } diff --git a/src/main/java/carpet/script/exception/Throwables.java b/src/main/java/carpet/script/exception/Throwables.java index 136aa09e..589ea740 100644 --- a/src/main/java/carpet/script/exception/Throwables.java +++ b/src/main/java/carpet/script/exception/Throwables.java @@ -13,24 +13,24 @@ public class Throwables { private final String id; private final Throwables parent; - private static final Map byValue = new HashMap<>(); + private static final Map byId = new HashMap<>(); private static final Map> handles = 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_TYPE = register("unknown_poi_type", 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 FILE_READ_EXCEPTION = register("file_read_exception", THROWN_EXCEPTION_TYPE); - public static final Throwables NBT_READ = register("nbt_read_exception", FILE_READ_EXCEPTION); - public static final Throwables JSON_READ = register("json_read_exception", FILE_READ_EXCEPTION); - public static final Throwables USER_DEFINED = register("user_defined_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_TYPE = register("unknown_poi_type", 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 @@ -43,7 +43,7 @@ public class Throwables { public static Throwables register(String id, Throwables parent) { Throwables exc = new Throwables(id, parent); - byValue.put(id, exc); + byId.put(id, exc); Set itHandles = new HashSet<>(); itHandles.add(exc); while (parent!=null) @@ -69,7 +69,7 @@ public class Throwables { 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); return properType; } diff --git a/src/main/java/carpet/script/language/ControlFlow.java b/src/main/java/carpet/script/language/ControlFlow.java index f5173f26..7972d582 100644 --- a/src/main/java/carpet/script/language/ControlFlow.java +++ b/src/main/java/carpet/script/language/ControlFlow.java @@ -8,7 +8,14 @@ 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 @@ -85,6 +92,25 @@ public class ControlFlow { LazyValue __ = c.getVariable("_"); 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) { @@ -105,7 +131,10 @@ public class ControlFlow { } } c.setVariable("_", __); - + if (_trace != null) + c.setVariable("_trace", _trace); + else + c.delVariable("_trace"); if (val == null) // not handled { throw ret; From 2d1b53258c13a8b4638b8974a86c7a21157f0550 Mon Sep 17 00:00:00 2001 From: gnembon Date: Tue, 9 Mar 2021 13:29:20 -0400 Subject: [PATCH 13/16] simplify error messages --- src/main/java/carpet/script/api/Auxiliary.java | 14 +++++++++----- src/main/java/carpet/script/api/Inventories.java | 2 +- src/main/java/carpet/script/api/Scoreboards.java | 2 +- src/main/java/carpet/script/api/WorldAccess.java | 12 ++++++------ .../carpet/script/exception/ThrowStatement.java | 8 ++++---- .../java/carpet/script/utils/ShapeDispatcher.java | 2 +- src/main/java/carpet/script/value/BlockValue.java | 2 +- .../carpet/script/value/NBTSerializableValue.java | 2 +- .../java/carpet/script/value/ValueConversions.java | 2 +- 9 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java index 63ebc142..39192581 100644 --- a/src/main/java/carpet/script/api/Auxiliary.java +++ b/src/main/java/carpet/script/api/Auxiliary.java @@ -162,10 +162,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 ThrowStatement("No such sound: "+soundName.getPath(), Throwables.UNKNOWN_SOUND); + throw new ThrowStatement(rawString, Throwables.UNKNOWN_SOUND); float volume = 1.0F; float pitch = 1.0F; SoundCategory mixer = SoundCategory.MASTER; @@ -861,7 +862,7 @@ public class Auxiliary { state = ((CarpetScriptHost) c.host).readFileTag(fdesc.getLeft(), fdesc.getRight()); } catch (CrashException e) { - throw new ThrowStatement("Couldn't read NBT data", Throwables.NBT_READ); + throw new ThrowStatement((fdesc.getRight()?"shared/":"")+ fdesc.getLeft(), Throwables.NBT_READ); } if (state == null) return LazyValue.NULL; @@ -879,7 +880,10 @@ public class Auxiliary { Throwable exc = e; if(e.getCause() != null) exc = e.getCause(); - throw new ThrowStatement("Failed to read JSON file: "+exc.getMessage(), Throwables.JSON_READ); + 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) @@ -973,7 +977,7 @@ public class Auxiliary { state = ((CarpetScriptHost)((CarpetContext)c).host).readFileTag(file, shared); } catch (CrashException e) { - throw new ThrowStatement("Failed to read App data", Throwables.NBT_READ); + throw new ThrowStatement("app data", Throwables.NBT_READ); } if (state == null) return (cc, tt) -> Value.NULL; diff --git a/src/main/java/carpet/script/api/Inventories.java b/src/main/java/carpet/script/api/Inventories.java index e3ad4353..96dd35e1 100644 --- a/src/main/java/carpet/script/api/Inventories.java +++ b/src/main/java/carpet/script/api/Inventories.java @@ -193,7 +193,7 @@ public class Inventories { String itemStr = lv.get(0).evalValue(c).getString(); Item item; Identifier id = new Identifier(itemStr); - item = Registry.ITEM.getOrEmpty(id).orElseThrow(() -> new ThrowStatement("Incorrect item: "+itemStr, Throwables.UNKNOWN_ITEM)); + 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()))); diff --git a/src/main/java/carpet/script/api/Scoreboards.java b/src/main/java/carpet/script/api/Scoreboards.java index c58f7c10..f0a2e598 100644 --- a/src/main/java/carpet/script/api/Scoreboards.java +++ b/src/main/java/carpet/script/api/Scoreboards.java @@ -133,7 +133,7 @@ public class Scoreboards { criterion = ScoreboardCriterion.createStatCriterion(critetionName).orElse(null); if (criterion==null) { - throw new ThrowStatement("Unknown scoreboard criterion: "+critetionName, Throwables.UNKNOWN_CRITERION); + 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 36652027..ff8640fb 100644 --- a/src/main/java/carpet/script/api/WorldAccess.java +++ b/src/main/java/carpet/script/api/WorldAccess.java @@ -339,7 +339,7 @@ public class WorldAccess { if (!"any".equals(poiType)) { PointOfInterestType type = Registry.POINT_OF_INTEREST_TYPE.getOrEmpty(new Identifier(poiType)) - .orElseThrow(() -> new ThrowStatement("Unknown POI type", Throwables.UNKNOWN_POI_TYPE)); + .orElseThrow(() -> new ThrowStatement(poiType, Throwables.UNKNOWN_POI_TYPE)); condition = (tt) -> tt == type; } if (locator.offset + 2 < lv.size()) @@ -394,7 +394,7 @@ public class WorldAccess { } String poiTypeString = poi.getString().toLowerCase(Locale.ROOT); PointOfInterestType type = Registry.POINT_OF_INTEREST_TYPE.getOrEmpty(new Identifier(poiTypeString)) - .orElseThrow(() -> new ThrowStatement("Unknown POI type: "+poiTypeString, Throwables.UNKNOWN_POI_TYPE)); + .orElseThrow(() -> new ThrowStatement(poiTypeString, Throwables.UNKNOWN_POI_TYPE)); int occupancy = 0; if (locator.offset + 1 < lv.size()) { @@ -874,7 +874,7 @@ public class WorldAccess { playerBreak = true; String itemString = val.getString(); item = Registry.ITEM.getOrEmpty(new Identifier(itemString)) - .orElseThrow(() -> new ThrowStatement("Incorrect item: " + itemString, Throwables.UNKNOWN_ITEM)); + .orElseThrow(() -> new ThrowStatement(itemString, Throwables.UNKNOWN_ITEM)); } } CompoundTag tag = null; @@ -1182,7 +1182,7 @@ public class WorldAccess { String biomeName = lv.get(locator.offset+0).evalValue(c).getString(); // from locatebiome command code Biome biome = cc.s.getMinecraftServer().getRegistryManager().get(Registry.BIOME_KEY).getOrEmpty(new Identifier(biomeName)) - .orElseThrow(() -> new ThrowStatement("Unknown biome: "+biomeName, Throwables.UNKNOWN_BIOME)); + .orElseThrow(() -> new ThrowStatement(biomeName, Throwables.UNKNOWN_BIOME)); boolean doImmediateUpdate = true; if (lv.size() > locator.offset+1) { @@ -1252,7 +1252,7 @@ public class WorldAccess { { String reqString = requested.getString(); structure = Registry.STRUCTURE_FEATURE.getOrEmpty(new Identifier(reqString)) - .orElseThrow(() -> new ThrowStatement("Unknown structure: " + reqString, Throwables.UNKNOWN_STRUCTURE)); + .orElseThrow(() -> new ThrowStatement(reqString, Throwables.UNKNOWN_STRUCTURE)); } if (lv.size() > locator.offset+1) { @@ -1332,7 +1332,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 ThrowStatement("Unknown structure: "+structureName, Throwables.UNKNOWN_STRUCTURE); + 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/ThrowStatement.java b/src/main/java/carpet/script/exception/ThrowStatement.java index 01bf9ccf..74dfa343 100644 --- a/src/main/java/carpet/script/exception/ThrowStatement.java +++ b/src/main/java/carpet/script/exception/ThrowStatement.java @@ -29,15 +29,15 @@ public class ThrowStatement extends InternalExpressionException * 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 thrownExceptionType An {@link Throwables} containing the inheritance data + * @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 thrownExceptionType) + public ThrowStatement(String message, Throwables type) { - super(message); + super(type.getId()); this.exceptionData = StringValue.of(message); - this.thrownExceptionType = thrownExceptionType; + this.thrownExceptionType = type; } @Override diff --git a/src/main/java/carpet/script/utils/ShapeDispatcher.java b/src/main/java/carpet/script/utils/ShapeDispatcher.java index 84aab23a..87a153f2 100644 --- a/src/main/java/carpet/script/utils/ShapeDispatcher.java +++ b/src/main/java/carpet/script/utils/ShapeDispatcher.java @@ -158,7 +158,7 @@ public class ShapeDispatcher } catch (CommandSyntaxException e) { - throw new ThrowStatement("No such particle: "+name, Throwables.UNKNOWN_PARTICLE); + 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 fe26f57f..27d21cdd 100644 --- a/src/main/java/carpet/script/value/BlockValue.java +++ b/src/main/java/carpet/script/value/BlockValue.java @@ -76,7 +76,7 @@ public class BlockValue extends Value catch (CommandSyntaxException ignored) { } - throw new ThrowStatement("Cannot parse block: "+str, Throwables.UNKNOWN_BLOCK); + 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/NBTSerializableValue.java b/src/main/java/carpet/script/value/NBTSerializableValue.java index 11595d06..9c26f252 100644 --- a/src/main/java/carpet/script/value/NBTSerializableValue.java +++ b/src/main/java/carpet/script/value/NBTSerializableValue.java @@ -299,7 +299,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa } catch (CommandSyntaxException e) { - throw new ThrowStatement("Incorrect item: "+itemString, Throwables.UNKNOWN_ITEM); + 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 320916ae..ba5bbf24 100644 --- a/src/main/java/carpet/script/value/ValueConversions.java +++ b/src/main/java/carpet/script/value/ValueConversions.java @@ -160,7 +160,7 @@ public class ValueConversions } } if (dim == null) - throw new ThrowStatement("Incorrect dimension string: "+dimString, Throwables.UNKNOWN_DIMENSION); + throw new ThrowStatement(dimString, Throwables.UNKNOWN_DIMENSION); return server.getWorld(dim); } } From 72af3f732e2b8fe3f0ba3dfdae6095e07502db0b Mon Sep 17 00:00:00 2001 From: gnembon Date: Tue, 9 Mar 2021 13:51:52 -0400 Subject: [PATCH 14/16] cleaned docs --- docs/scarpet/api/Auxiliary.md | 8 ++++---- docs/scarpet/api/BlocksAndWorldAccess.md | 18 ++++++++---------- .../java/carpet/script/api/WorldAccess.java | 8 +++----- .../carpet/script/exception/Throwables.java | 2 +- 4 files changed, 16 insertions(+), 20 deletions(-) diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index d9209f5a..1a7d3c33 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -328,8 +328,8 @@ write is beneficial for writing speed. To send multiple packs of data, either pr third argument. Throws: -- `nbt_read_exception`: When failed to read NBT file -- `json_read_exception`: When failed to read JSON file. The `_msg` variable will contain details about the problem +- `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']);
@@ -382,7 +382,7 @@ 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_exception` if failed to read app data.
+Throws `nbt_read_error` if failed to read app data.
 
 ### `store_app_data(tag)`
 
@@ -538,7 +538,7 @@ 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 given a dimension as a String and it can't be found.
+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 fdc3e82c..3f27bb4d 100644
--- a/docs/scarpet/api/BlocksAndWorldAccess.md
+++ b/docs/scarpet/api/BlocksAndWorldAccess.md
@@ -21,7 +21,7 @@ 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 block from `state` doesn't exist.
+Throws `unknown_block` if provided input is not valid.
 
 
set(10,10,10,'stone');
 scan(10,10,10,0,0,0, b = _);
@@ -52,7 +52,7 @@ 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 `block` doesn't exist.
+Throws `unknown_block` if provided block to set is not valid
 
 
 set(0,5,0,'bedrock')  => bedrock
@@ -152,7 +152,7 @@ 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_type` if POI type doesn't exist 
+Throws `unknown_poi` if the provided point of interest doesn't exist 
 
 ### `set_biome(pos, biome_name, update=true)`
 
@@ -164,7 +164,7 @@ 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 biome doesn't exist.
+Throws `unknown_biome` if the `biome_name` doesn't exist.
 
 ### `update(pos)`
 
@@ -283,7 +283,7 @@ 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 `block` comes from a String and doesn't exist.
+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, ...}
@@ -305,7 +305,7 @@ 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` comes from a String and doesn't exist
+Throws `unknown_block` if `block` doesn't exist
 
 ### `block_data(pos)`
 
@@ -341,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:
@@ -364,8 +364,6 @@ and values < 0, below sea level.
 * `'features'`: list of features that generate in the biome, grouped by generation steps
 * `'structures'`: list of structures that generate in the biome.
 
-Throws `unknown_block` if `block` comes from a String and doesn't exist. 
-
 ### `solid(pos)`
 
 Boolean function, true if the block is solid.
diff --git a/src/main/java/carpet/script/api/WorldAccess.java b/src/main/java/carpet/script/api/WorldAccess.java
index ff8640fb..46b1cbbe 100644
--- a/src/main/java/carpet/script/api/WorldAccess.java
+++ b/src/main/java/carpet/script/api/WorldAccess.java
@@ -61,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;
@@ -108,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;
@@ -339,7 +337,7 @@ public class WorldAccess {
                 if (!"any".equals(poiType))
                 {
                     PointOfInterestType type =  Registry.POINT_OF_INTEREST_TYPE.getOrEmpty(new Identifier(poiType))
-                            .orElseThrow(() -> new ThrowStatement(poiType, Throwables.UNKNOWN_POI_TYPE));
+                            .orElseThrow(() -> new ThrowStatement(poiType, Throwables.UNKNOWN_POI));
                     condition = (tt) -> tt == type;
                 }
                 if (locator.offset + 2 < lv.size())
@@ -394,7 +392,7 @@ public class WorldAccess {
             }
             String poiTypeString = poi.getString().toLowerCase(Locale.ROOT);
             PointOfInterestType type =  Registry.POINT_OF_INTEREST_TYPE.getOrEmpty(new Identifier(poiTypeString))
-            		.orElseThrow(() -> new ThrowStatement(poiTypeString, Throwables.UNKNOWN_POI_TYPE));
+            		.orElseThrow(() -> new ThrowStatement(poiTypeString, Throwables.UNKNOWN_POI));
             int occupancy = 0;
             if (locator.offset + 1 < lv.size())
             {
@@ -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
             {
diff --git a/src/main/java/carpet/script/exception/Throwables.java b/src/main/java/carpet/script/exception/Throwables.java
index 589ea740..40f45b17 100644
--- a/src/main/java/carpet/script/exception/Throwables.java
+++ b/src/main/java/carpet/script/exception/Throwables.java
@@ -23,7 +23,7 @@ public class Throwables {
     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_TYPE      = register("unknown_poi_type", 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);

From 29893ed28ac3cea5c1600c2106597a6796a01532 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Tue, 9 Mar 2021 14:43:29 -0400
Subject: [PATCH 15/16] renames

---
 .../script/exception/ExpressionException.java      | 10 +++++-----
 .../carpet/script/exception/ThrowStatement.java    | 14 +++++++-------
 2 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/src/main/java/carpet/script/exception/ExpressionException.java b/src/main/java/carpet/script/exception/ExpressionException.java
index c62e3db7..419fb345 100644
--- a/src/main/java/carpet/script/exception/ExpressionException.java
+++ b/src/main/java/carpet/script/exception/ExpressionException.java
@@ -16,7 +16,7 @@ public class ExpressionException extends RuntimeException implements ResolvedExc
     public final Context context;
     public final Tokenizer.Token token;
     public final List stack = new ArrayList<>();
-    private final Supplier lazyStacktrace;
+    private final Supplier lazyMessage;
     private String cachedMessage = null;
 
     public ExpressionException(Context c, Expression e, String message)
@@ -27,7 +27,7 @@ public class ExpressionException extends RuntimeException implements ResolvedExc
     public ExpressionException(Context c, Expression e, Tokenizer.Token t, String message)
     {
         super("Error");
-        lazyStacktrace = () -> makeMessage(c, e, t, message);
+        lazyMessage = () -> makeMessage(c, e, t, message);
         token = t;
         context = c;
     }
@@ -35,7 +35,7 @@ public class ExpressionException extends RuntimeException implements ResolvedExc
     {
         super("Error");
         this.stack.addAll(stack);
-        lazyStacktrace = () -> makeMessage(c, e, t, message);
+        lazyMessage = () -> makeMessage(c, e, t, message);
         token = t;
         context = c;
     }
@@ -44,7 +44,7 @@ public class ExpressionException extends RuntimeException implements ResolvedExc
     {
         super("Error");
         this.stack.addAll(stack);
-        lazyStacktrace = () -> makeMessage(c, e, t, messageSupplier.get());
+        lazyMessage = () -> makeMessage(c, e, t, messageSupplier.get());
         token = t;
         context = c;
     }
@@ -89,7 +89,7 @@ public class ExpressionException extends RuntimeException implements ResolvedExc
     public String getMessage() {
         if (cachedMessage == null)
         {
-        	cachedMessage = lazyStacktrace.get();
+        	cachedMessage = lazyMessage.get();
         }
         return cachedMessage;
     }
diff --git a/src/main/java/carpet/script/exception/ThrowStatement.java b/src/main/java/carpet/script/exception/ThrowStatement.java
index 74dfa343..f667fdbc 100644
--- a/src/main/java/carpet/script/exception/ThrowStatement.java
+++ b/src/main/java/carpet/script/exception/ThrowStatement.java
@@ -8,8 +8,8 @@ import carpet.script.value.Value;
 
 public class ThrowStatement extends InternalExpressionException
 {
-    private final Throwables thrownExceptionType;
-    private final Value exceptionData;
+    private final Throwables type;
+    private final Value data;
     
     /**
      * Creates a throw exception from a value, and assigns it a specified message.
@@ -20,8 +20,8 @@ public class ThrowStatement extends InternalExpressionException
     public ThrowStatement(Value data, Throwables type)
     {
         super(type.getId());
-        exceptionData = data;
-        thrownExceptionType = type;
+        this.data = data;
+        this.type = type;
     }
 
     /**
@@ -36,12 +36,12 @@ public class ThrowStatement extends InternalExpressionException
     public ThrowStatement(String message, Throwables type)
     {
         super(type.getId());
-        this.exceptionData = StringValue.of(message);
-        this.thrownExceptionType = type;
+        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, thrownExceptionType, exceptionData);
+        return new ProcessedThrowStatement(c, e, token, stack, type, data);
     }
 }

From 5a366cca9e13d0da70c55a9a6386cfe9d925ff03 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Tue, 9 Mar 2021 19:32:51 -0400
Subject: [PATCH 16/16] added subtype option for thrown exceptions

---
 .../language/FunctionsAndControlFlow.md       |  6 +++--
 docs/scarpet/resources/editors/idea/3.txt     |  1 +
 .../scarpet/resources/editors/npp/scarpet.xml |  2 +-
 .../script/exception/ThrowStatement.java      |  7 ++++++
 .../carpet/script/exception/Throwables.java   | 23 ++++++-------------
 .../carpet/script/language/ControlFlow.java   | 10 ++++----
 6 files changed, 26 insertions(+), 23 deletions(-)

diff --git a/docs/scarpet/language/FunctionsAndControlFlow.md b/docs/scarpet/language/FunctionsAndControlFlow.md
index 73d5d1fc..a02c2d51 100644
--- a/docs/scarpet/language/FunctionsAndControlFlow.md
+++ b/docs/scarpet/language/FunctionsAndControlFlow.md
@@ -295,10 +295,12 @@ 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(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 `user_exception` 
+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/script/exception/ThrowStatement.java b/src/main/java/carpet/script/exception/ThrowStatement.java
index f667fdbc..c61470cb 100644
--- a/src/main/java/carpet/script/exception/ThrowStatement.java
+++ b/src/main/java/carpet/script/exception/ThrowStatement.java
@@ -24,6 +24,13 @@ public class ThrowStatement extends InternalExpressionException
         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 diff --git a/src/main/java/carpet/script/exception/Throwables.java b/src/main/java/carpet/script/exception/Throwables.java index 40f45b17..22857a41 100644 --- a/src/main/java/carpet/script/exception/Throwables.java +++ b/src/main/java/carpet/script/exception/Throwables.java @@ -1,9 +1,7 @@ package carpet.script.exception; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; /** * This class contains default Scarpet catchable types, as well as their inheritance and @@ -14,7 +12,6 @@ public class Throwables { private final Throwables parent; private static final Map byId = new HashMap<>(); - private static final Map> handles = new HashMap<>(); public static final Throwables THROWN_EXCEPTION_TYPE = register("exception", null); public static final Throwables VALUE_EXCEPTION = register("value_exception", THROWN_EXCEPTION_TYPE); @@ -44,14 +41,6 @@ public class Throwables { { Throwables exc = new Throwables(id, parent); byId.put(id, exc); - Set itHandles = new HashSet<>(); - itHandles.add(exc); - while (parent!=null) - { - handles.get(parent.id).add(exc); - parent = parent.parent; - } - handles.put(id, itHandles); return exc; } @@ -61,7 +50,7 @@ public class Throwables { * Use an {@link InternalExpressionException} for that * @param id The exception's value as a {@link String} */ - protected Throwables(String id, Throwables parent) + public Throwables(String id, Throwables parent) { this.id = id; this.parent = parent; @@ -74,7 +63,6 @@ public class Throwables { return properType; } - /** * Checks whether the given filter matches an instance of this exception, by checking equality * with itself and possible parents. @@ -83,9 +71,12 @@ public class Throwables { */ public boolean isRelevantFor(String filter) { - Set relevantSet = handles.get(filter); - if (relevantSet == null) throw new InternalExpressionException("Unknown exception type: "+filter); - return relevantSet.contains(this); + return (id.equals(filter) || (parent != null && parent.isRelevantFor(filter))); + } + + public boolean isUserException() + { + return this == USER_DEFINED || parent == USER_DEFINED; } /** diff --git a/src/main/java/carpet/script/language/ControlFlow.java b/src/main/java/carpet/script/language/ControlFlow.java index 7972d582..e094309e 100644 --- a/src/main/java/carpet/script/language/ControlFlow.java +++ b/src/main/java/carpet/script/language/ControlFlow.java @@ -61,8 +61,10 @@ public class ControlFlow { 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 2 parameters"); + throw new InternalExpressionException("throw() can't accept more than 3 parameters"); } }); @@ -79,7 +81,7 @@ public class ControlFlow { { if (lv.size() == 1) { - if (ret.thrownExceptionType != Throwables.USER_DEFINED) + if (!ret.thrownExceptionType.isUserException()) throw ret; return (c_, t_) -> Value.NULL; } @@ -101,7 +103,7 @@ public class ControlFlow { NumericValue.of(f.getToken().linepos+1) )).collect(Collectors.toList())), - StringValue.of("locals"), MapValue.wrap(ret.context.variables.entrySet().stream().collect(Collectors.toMap( + 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) ))), @@ -114,7 +116,7 @@ public class ControlFlow { if (lv.size() == 2) { - if (ret.thrownExceptionType == Throwables.USER_DEFINED) + if (ret.thrownExceptionType.isUserException()) val = lv.get(1).evalValue(c, t); } else