From a155494d1183835a2d316bce3f808c12e282c5db Mon Sep 17 00:00:00 2001 From: altrisi Date: Tue, 12 Jan 2021 19:24:16 +0100 Subject: [PATCH] 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); } }