diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index 9148fb78..952e0f15 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -298,7 +298,8 @@ Resource is identified by a path to the file. A path can contain letters, numbers, characters `-`, `+`, or `_`, and a folder separator: `'/'`. Any other characters are stripped from the name. Empty descriptors are invalid, except for `list_files` where it means the root folder. Do not add file extensions to the descriptor - extensions are inferred -based on the `type` of the file. +based on the `type` of the file. A path can have one `'.zip'` component indicating a zip folder allowing to read / write to and from +zip files, although you cannot nest zip files in other zip files. Resources can be located in the app specific space, or a shared space for all the apps. Accessing of app-specific resources is guaranteed to be isolated from other apps. Shared resources are... well, shared across all apes, meaning @@ -312,7 +313,7 @@ specific data directory is under `world/scripts/foo.data/...`, and shared data s The default no-name app, via `/script run` command can only save/load/read files from the shared space. -Functions return `null` if an error is encounter or no file is present (for read, list and delete operations). Returns `true` +Functions return `null` if no file is present (for read, list and delete operations). Returns `true` for success writes and deletes, and requested data, based on the file type, for read operations. It returns list of files for folder listing. @@ -336,6 +337,10 @@ third argument. Throws: - `nbt_read_error`: When failed to read NBT file. - `json_read_error`: When failed to read JSON file. The exception data will contain details about the problem. +- `io_exception`: For all other errors when handling data on disk not related to encoding issues + +All other errors resulting of improper use of input arguments should result in `null` returned from the function, rather than exception +thrown.
write_file('foo', 'shared_text, ['one', 'two']);
diff --git a/docs/scarpet/language/FunctionsAndControlFlow.md b/docs/scarpet/language/FunctionsAndControlFlow.md
index a02c2d51..a0a867e0 100644
--- a/docs/scarpet/language/FunctionsAndControlFlow.md
+++ b/docs/scarpet/language/FunctionsAndControlFlow.md
@@ -265,8 +265,8 @@ but like everywhere else, doing that sounds like a bad idea.
`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.
+ - `nbt_error`: Incorrect input/output NBT file.
+ - `json_error`: Incorrect input/output JSON data.
- `user_exception`: Exception thrown by default with `throw` function.
Synopsis:
diff --git a/src/main/java/carpet/script/CarpetScriptHost.java b/src/main/java/carpet/script/CarpetScriptHost.java
index 2432024a..b6dc0e85 100644
--- a/src/main/java/carpet/script/CarpetScriptHost.java
+++ b/src/main/java/carpet/script/CarpetScriptHost.java
@@ -3,6 +3,7 @@ package carpet.script;
import carpet.CarpetServer;
import carpet.CarpetSettings;
import carpet.script.api.Auxiliary;
+import carpet.script.argument.FileArgument;
import carpet.script.argument.FunctionArgument;
import carpet.script.bundled.Module;
import carpet.script.command.CommandArgument;
@@ -347,7 +348,7 @@ public class CarpetScriptHost extends ScriptHost
if (main == null) return false;
if (getFunction("__command") == null) return false;
- if (scriptServer.isValidCommandRoot(getName()))
+ if (scriptServer.isInvalidCommandRoot(getName()))
{
notifier.accept(Messenger.c("gi Tried to mask vanilla command."));
return null;
@@ -785,31 +786,31 @@ public class CarpetScriptHost extends ScriptHost
private void dumpState()
{
- Module.saveData(main, null, globalState, false);
+ Module.saveData(main, globalState);
}
private Tag loadState()
{
- return Module.getData(main, null, false);
+ return Module.getData(main);
}
- public Tag readFileTag(String file, boolean isShared)
+ public Tag readFileTag(FileArgument fdesc)
{
- if (getName() == null && !isShared) return null;
- if (file != null)
- return Module.getData(main, file, isShared);
+ if (getName() == null && !fdesc.isShared) return null;
+ if (fdesc.resource != null)
+ return fdesc.getNbtData(main);
if (parent == null)
return globalState;
return ((CarpetScriptHost)parent).globalState;
}
- public boolean writeTagFile(Tag tag, String file, boolean isShared)
+ public boolean writeTagFile(Tag tag, FileArgument fdesc)
{
- if (getName() == null && !isShared) return false; // if belongs to an app, cannot be default host.
+ if (getName() == null && !fdesc.isShared) return false; // if belongs to an app, cannot be default host.
- if (file!= null)
+ if (fdesc.resource != null)
{
- return Module.saveData(main, file, tag, isShared);
+ return fdesc.saveNbtData(main, tag);
}
CarpetScriptHost responsibleHost = (parent != null)?(CarpetScriptHost) parent:this;
@@ -822,34 +823,34 @@ public class CarpetScriptHost extends ScriptHost
return true;
}
- public boolean removeResourceFile(String resource, boolean isShared, String type)
+ public boolean removeResourceFile(FileArgument fdesc)
{
- if (getName() == null && !isShared) return false; //
- return Module.dropExistingFile(main, resource, type, isShared);
+ if (getName() == null && !fdesc.isShared) return false; //
+ return fdesc.dropExistingFile(main);
}
- public boolean appendLogFile(String resource, boolean isShared, String type, List data)
+ public boolean appendLogFile(FileArgument fdesc, List data)
{
- if (getName() == null && !isShared) return false; // if belongs to an app, cannot be default host.
- return Module.appendToTextFile(main, resource, type, isShared, data);
+ if (getName() == null && !fdesc.isShared) return false; // if belongs to an app, cannot be default host.
+ return fdesc.appendToTextFile(main, data);
}
- public List readTextResource(String resource, String type, boolean isShared)
+ public List readTextResource(FileArgument fdesc)
{
- if (getName() == null && !isShared) return null;
- return Module.listFile(main, resource, type, isShared);
+ if (getName() == null && !fdesc.isShared) return null;
+ return fdesc.listFile(main);
}
- public JsonElement readJsonFile(String resource, String type, boolean isShared)
+ public JsonElement readJsonFile(FileArgument fdesc)
{
- if (getName() == null && !isShared) return null;
- return Module.readJsonFile(main, resource, type, isShared);
+ if (getName() == null && !fdesc.isShared) return null;
+ return fdesc.readJsonFile(main);
}
- public Stream listFolder(String resource, String type, boolean isShared)
+ public Stream listFolder(FileArgument fdesc)
{
- if (getName() == null && !isShared) return null; //
- return Module.listFolder(main, resource, type, isShared);
+ if (getName() == null && !fdesc.isShared) return null; //
+ return fdesc.listFolder(main);
}
diff --git a/src/main/java/carpet/script/CarpetScriptServer.java b/src/main/java/carpet/script/CarpetScriptServer.java
index 8a93f8c2..cd02173b 100644
--- a/src/main/java/carpet/script/CarpetScriptServer.java
+++ b/src/main/java/carpet/script/CarpetScriptServer.java
@@ -305,9 +305,9 @@ public class CarpetScriptServer
return true;
}
- public boolean isValidCommandRoot(String appName)
+ public boolean isInvalidCommandRoot(String appName)
{
- return !holyMoly.contains(appName);
+ return holyMoly.contains(appName);
}
diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java
index a1aaec2f..f540e468 100644
--- a/src/main/java/carpet/script/api/Auxiliary.java
+++ b/src/main/java/carpet/script/api/Auxiliary.java
@@ -7,7 +7,7 @@ import carpet.fakes.ServerWorldInterface;
import carpet.fakes.StatTypeInterface;
import carpet.fakes.ThreadedAnvilChunkStorageInterface;
import carpet.helpers.FeatureGenerator;
-import carpet.script.bundled.Module;
+import carpet.script.argument.FileArgument;
import carpet.script.CarpetContext;
import carpet.script.CarpetEventServer;
import carpet.script.CarpetScriptHost;
@@ -99,7 +99,6 @@ import net.minecraft.world.level.UnmodifiableLevelProperties;
import net.minecraft.world.level.storage.LevelStorage;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.tuple.Pair;
-import org.apache.commons.lang3.tuple.Triple;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -133,6 +132,7 @@ public class Auxiliary {
private static final Map mixerMap = Arrays.stream(SoundCategory.values()).collect(Collectors.toMap(SoundCategory::getName, k -> k));
public static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().registerTypeAdapter(Value.class, new ScarpetJsonDeserializer()).create();
+ @Deprecated
public static String recognizeResource(Value value, boolean isFloder)
{
String origfile = value.getString();
@@ -145,20 +145,6 @@ public class Auxiliary {
return file;
}
- public static Triple getFileDescriptor(List lv, Context c, boolean isFloder)
- {
- if (lv.size() < 2) throw new InternalExpressionException("File functions require path and type as first two arguments");
- String resource = recognizeResource(lv.get(0).evalValue(c), isFloder);
- String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT);
- boolean shared = origtype.startsWith("shared_");
- String type = shared ? origtype.substring(7) : origtype; //len(shared_)
- if (!Module.supportedTypes.containsKey(type))
- throw new InternalExpressionException("Unsupported file type: "+origtype);
- if (type.equals("folder") && !isFloder)
- throw new InternalExpressionException("Folder types are no supported for this IO function");
- return Triple.of(resource, type, shared);
- }
-
public static void apply(Expression expression)
{
expression.addLazyFunction("sound", -1, (c, t, lv) -> {
@@ -854,49 +840,27 @@ public class Auxiliary {
expression.addLazyFunction("list_files", 2, (c, t, lv) ->
{
- Triple fdesc = getFileDescriptor(lv, c, true);
- Stream files = ((CarpetScriptHost) c.host).listFolder(fdesc.getLeft(), fdesc.getMiddle(), fdesc.getRight());
+ FileArgument fdesc = FileArgument.from(lv, c, true, FileArgument.Reason.READ);
+ Stream files = ((CarpetScriptHost) c.host).listFolder(fdesc);
if (files == null) return LazyValue.NULL;
Value ret = ListValue.wrap(files.map(StringValue::of).collect(Collectors.toList()));
return (cc, tt) -> ret;
});
-
expression.addLazyFunction("read_file", 2, (c, t, lv) ->
{
- Triple fdesc = getFileDescriptor(lv, c, false);
+ FileArgument fdesc = FileArgument.from(lv, c, false, FileArgument.Reason.READ);
Value retVal;
- if (fdesc.getMiddle().equals("nbt"))
+ if (fdesc.type == FileArgument.Type.NBT)
{
- Tag state;
- try
- {
- state = ((CarpetScriptHost) c.host).readFileTag(fdesc.getLeft(), fdesc.getRight());
- } catch (CrashException e)
- {
- throw new ThrowStatement((fdesc.getRight()?"shared/":"")+ fdesc.getLeft(), Throwables.NBT_READ);
- }
-
+ Tag state = ((CarpetScriptHost) c.host).readFileTag(fdesc);
if (state == null) return LazyValue.NULL;
retVal = new NBTSerializableValue(state);
}
- else if (fdesc.getMiddle().equals("json"))
+ else if (fdesc.type == FileArgument.Type.JSON)
{
JsonElement json;
- try
- {
- json = ((CarpetScriptHost) c.host).readJsonFile(fdesc.getLeft(), fdesc.getMiddle(), fdesc.getRight());
- }
- catch (JsonParseException e)
- {
- Throwable exc = e;
- if(e.getCause() != null)
- exc = e.getCause();
- throw new ThrowStatement(MapValue.wrap(ImmutableMap.of(
- StringValue.of("error"), StringValue.of(exc.getMessage()),
- StringValue.of("path"), StringValue.of((fdesc.getRight()?"shared/":"")+ fdesc.getLeft())
- )), Throwables.JSON_READ);
- }
+ json = ((CarpetScriptHost) c.host).readJsonFile(fdesc);
Value parsedJson = GSON.fromJson(json, Value.class);
if (parsedJson == null)
retVal = Value.NULL;
@@ -905,7 +869,7 @@ public class Auxiliary {
}
else
{
- List content = ((CarpetScriptHost) c.host).readTextResource(fdesc.getLeft(), fdesc.getMiddle(), fdesc.getRight());
+ List content = ((CarpetScriptHost) c.host).readTextResource(fdesc);
if (content == null) return LazyValue.NULL;
retVal = ListValue.wrap(content.stream().map(StringValue::new).collect(Collectors.toList()));
}
@@ -913,32 +877,29 @@ public class Auxiliary {
});
expression.addLazyFunction("delete_file", 2, (c, t, lv) -> {
- Triple fdesc = getFileDescriptor(lv, c, false);
- return ((CarpetScriptHost) c.host).removeResourceFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle())
- ? LazyValue.TRUE
- : LazyValue.FALSE;
+ FileArgument fdesc = FileArgument.from(lv, c, false, FileArgument.Reason.DELETE);
+ return ((CarpetScriptHost) c.host).removeResourceFile(fdesc) ? LazyValue.TRUE : LazyValue.FALSE;
});
expression.addLazyFunction("write_file", -1, (c, t, lv) -> {
if (lv.size() < 3) throw new InternalExpressionException("'write_file' requires three or more arguments");
- Triple fdesc = getFileDescriptor(lv, c, false);
-
+ FileArgument fdesc = FileArgument.from(lv, c, false, FileArgument.Reason.CREATE);
boolean success;
- if (fdesc.getMiddle().equals("nbt"))
+ if (fdesc.type == FileArgument.Type.NBT)
{
Value val = lv.get(2).evalValue(c);
NBTSerializableValue tagValue = (val instanceof NBTSerializableValue)
? (NBTSerializableValue) val
: new NBTSerializableValue(val.getString());
Tag tag = tagValue.getTag();
- success = ((CarpetScriptHost) c.host).writeTagFile(tag, fdesc.getLeft(), fdesc.getRight());
+ success = ((CarpetScriptHost) c.host).writeTagFile(tag, fdesc);
}
- else if (fdesc.getMiddle().equals("json"))
+ else if (fdesc.type == FileArgument.Type.JSON)
{
List data = Collections.singletonList(GSON.toJson(lv.get(2).evalValue(c).toJson()));
- ((CarpetScriptHost) c.host).removeResourceFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle());
- success = ((CarpetScriptHost) c.host).appendLogFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle(), data);
+ ((CarpetScriptHost) c.host).removeResourceFile(fdesc);
+ success = ((CarpetScriptHost) c.host).appendLogFile(fdesc, data);
}
else
{
@@ -963,34 +924,22 @@ public class Auxiliary {
data.add(lv.get(i).evalValue(c).getString());
}
}
- success = ((CarpetScriptHost) c.host).appendLogFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle(), data);
+ success = ((CarpetScriptHost) c.host).appendLogFile(fdesc, data);
}
return success?LazyValue.TRUE:LazyValue.FALSE;
});
- //write_file
-
expression.addLazyFunction("load_app_data", -1, (c, t, lv) ->
{
- String file = null;
- boolean shared = false;
+ FileArgument fdesc = new FileArgument(null, FileArgument.Type.NBT, null, false, false, FileArgument.Reason.READ);
if (lv.size()>0)
{
c.host.issueDeprecation("load_app_data(...) with arguments");
- file = recognizeResource(lv.get(0).evalValue(c), false);
- if (lv.size() > 1)
- {
- shared = lv.get(1).evalValue(c).getBoolean();
- }
- }
- Tag state;
- try
- {
- state = ((CarpetScriptHost)((CarpetContext)c).host).readFileTag(file, shared);
- } catch (CrashException e)
- {
- throw new ThrowStatement("app data", Throwables.NBT_READ);
+ String resource = recognizeResource(lv.get(0).evalValue(c), false);
+ boolean shared = lv.size() > 1 && lv.get(1).evalValue(c).getBoolean();
+ fdesc = new FileArgument(resource, FileArgument.Type.NBT, null, false, shared, FileArgument.Reason.READ);
}
+ Tag state = ((CarpetScriptHost) c.host).readFileTag(fdesc);
if (state == null)
return (cc, tt) -> Value.NULL;
Value retVal = new NBTSerializableValue(state);
@@ -1002,22 +951,19 @@ public class Auxiliary {
if (lv.size() == 0)
throw new InternalExpressionException("'store_app_data' needs NBT tag and an optional file");
Value val = lv.get(0).evalValue(c);
- String file = null;
- boolean shared = false;
+ FileArgument fdesc = new FileArgument(null, FileArgument.Type.NBT, null, false, false, FileArgument.Reason.CREATE);
if (lv.size()>1)
{
c.host.issueDeprecation("store_app_data(...) with more than one argument");
- file = recognizeResource(lv.get(1).evalValue(c), false);
- if (lv.size() > 2)
- {
- shared = lv.get(2).evalValue(c).getBoolean();
- }
+ String resource = recognizeResource(lv.get(1).evalValue(c), false);
+ boolean shared = lv.size() > 2 && lv.get(2).evalValue(c).getBoolean();
+ fdesc = new FileArgument(resource, FileArgument.Type.NBT, null, false, shared, FileArgument.Reason.CREATE);
}
NBTSerializableValue tagValue = (val instanceof NBTSerializableValue)
? (NBTSerializableValue) val
: new NBTSerializableValue(val.getString());
Tag tag = tagValue.getTag();
- boolean success = ((CarpetScriptHost)((CarpetContext)c).host).writeTagFile(tag, file, shared);
+ boolean success = ((CarpetScriptHost) c.host).writeTagFile(tag, fdesc);
return success?LazyValue.TRUE:LazyValue.FALSE;
});
diff --git a/src/main/java/carpet/script/argument/FileArgument.java b/src/main/java/carpet/script/argument/FileArgument.java
index f3ad42fe..8fbef1c5 100644
--- a/src/main/java/carpet/script/argument/FileArgument.java
+++ b/src/main/java/carpet/script/argument/FileArgument.java
@@ -5,34 +5,47 @@ import carpet.script.Context;
import carpet.script.LazyValue;
import carpet.script.bundled.Module;
import carpet.script.exception.InternalExpressionException;
+import carpet.script.exception.ThrowStatement;
+import carpet.script.exception.Throwables;
+import carpet.script.value.MapValue;
+import carpet.script.value.StringValue;
import carpet.script.value.Value;
+import com.google.common.collect.ImmutableMap;
import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.google.gson.stream.JsonReader;
+import com.sun.nio.zipfs.ZipPath;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.PositionTracker;
import net.minecraft.nbt.Tag;
import net.minecraft.nbt.TagReaders;
import net.minecraft.util.WorldSavePath;
+import net.minecraft.util.crash.CrashException;
import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
+import java.net.URI;
import java.nio.charset.StandardCharsets;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystemNotFoundException;
+import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -47,9 +60,22 @@ public class FileArgument
public boolean isFolder;
public boolean isShared;
public Reason reason;
+ private FileSystem zfs;
+ private Path zipPath;
public final static Object writeIOSync = new Object();
+ public void close() {
+ if (zfs != null && zfs.isOpen()) {
+ try {
+ zfs.close();
+ } catch (IOException e) {
+ throw new InternalExpressionException("Unable to close zip container: "+zipContainer);
+ }
+ zfs = null;
+ }
+ }
+
public enum Type
{
RAW("raw", ".txt"),
@@ -75,7 +101,7 @@ public class FileArgument
READ, CREATE, DELETE
}
- private FileArgument(String resource, Type type, String zipContainer, boolean isFolder, boolean isShared, Reason reason)
+ public FileArgument(String resource, Type type, String zipContainer, boolean isFolder, boolean isShared, Reason reason)
{
this.resource = resource;
this.type = type;
@@ -83,47 +109,103 @@ public class FileArgument
this.isFolder = isFolder;
this.isShared = isShared;
this.reason = reason;
+ this.zfs = null;
+ this.zipPath = null;
+ }
+
+ @Override
+ public String toString() {
+ return "path: "+resource+" zip: "+zipContainer+" type: "+type.id+" folder: "+isFolder+" shared: "+isShared+" reason: "+reason.toString();
}
public static FileArgument from(List lv, Context c, boolean isFolder, Reason reason)
{
if (lv.size() < 2) throw new InternalExpressionException("File functions require path and type as first two arguments");
- String resource = recognizeResource(lv.get(0).evalValue(c), isFolder);
+ Pair resource = recognizeResource(lv.get(0).evalValue(c), isFolder);
String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT);
boolean shared = origtype.startsWith("shared_");
String typeString = shared ? origtype.substring(7) : origtype; //len(shared_)
Type type = Type.of.get(typeString);
- if (!Type.of.containsKey(typeString))
+ if (type==null)
throw new InternalExpressionException("Unsupported file type: "+origtype);
-
if (type==Type.FOLDER && !isFolder)
throw new InternalExpressionException("Folder types are no supported for this IO function");
- return new FileArgument(resource, type,null, isFolder, shared, reason);
+ return new FileArgument(resource.getLeft(), type,resource.getRight(), isFolder, shared, reason);
}
- public static String recognizeResource(Value value, boolean isFolder)
+ public static Pair recognizeResource(Value value, boolean isFolder)
{
String origfile = value.getString();
- String file = origfile.toLowerCase(Locale.ROOT).replaceAll("[^A-Za-z0-9\\-+_/]", "");
- file = Arrays.stream(file.split("/+")).filter(s -> !s.isEmpty()).collect(Collectors.joining("/"));
- if (file.isEmpty() && !isFolder)
+ String[] pathElements = origfile.toLowerCase(Locale.ROOT).split("[/\\\\]+");
+ List path = new ArrayList<>();
+ String zipPath = null;
+ for (String token : pathElements)
{
- throw new InternalExpressionException("Cannot use "+origfile+" as resource name - must have some letters and numbers");
+ boolean isZip = token.endsWith(".zip");
+ if (zipPath != null && isZip) throw new InternalExpressionException(token+" indicates zip access in an already zipped location "+zipPath);
+ if (isZip) token = token.substring(0, token.length()-4);
+ token = token.replaceAll("[^A-Za-z0-9\\-+_]", "");
+ if (token.isEmpty()) continue;
+ if (isZip) token = token + ".zip";
+ path.add(token);
+ if (isZip)
+ {
+ zipPath = String.join("/", path);
+ path.clear();
+ }
}
- return file;
+ if (path.isEmpty() && !isFolder)
+ throw new InternalExpressionException(
+ "Cannot use "+origfile+" as resource name: indicated path is empty"+((zipPath==null)?"":" in zip container "+zipPath)
+ );
+ return Pair.of(String.join("/", path), zipPath);
+ }
+
+ private Path resolve(String suffix)
+ {
+ if (CarpetServer.minecraft_server == null)
+ throw new InternalExpressionException("Accessing world files without server running");
+ return CarpetServer.minecraft_server.getSavePath(WorldSavePath.ROOT).resolve("scripts/"+suffix);
}
private Path toPath(Module module)
{
if (!isShared && (module == null || module.getName() == null)) return null;
- return CarpetServer.minecraft_server.getSavePath(WorldSavePath.ROOT).resolve("scripts/"+getDescriptor(module, resource)+type.extension);
+ if (zipContainer == null)
+ return resolve(getDescriptor(module, resource)+(isFolder?"":type.extension));
+ else
+ {
+ if (zfs == null)
+ {
+ Map env = new HashMap<>();
+ if (reason == Reason.CREATE) env.put("create", "true");
+ zipPath = resolve(getDescriptor(module, zipContainer));
+ try {
+ zfs = FileSystems.newFileSystem(URI.create("jar:"+ zipPath.toUri().toString()), env);
+ }
+ catch (FileSystemNotFoundException fsnfe)
+ {
+ return null;
+ }
+ catch (IOException e)
+ {
+ throw new ThrowStatement("Unable to open zip file: "+zipContainer, Throwables.IO_EXCEPTION);
+ }
+ }
+ return zfs.getPath(resource+(isFolder?"/":type.extension));
+ }
}
private Path moduleRootPath(Module module)
{
if (!isShared && (module == null || module.getName() == null)) return null;
- return CarpetServer.minecraft_server.getSavePath(WorldSavePath.ROOT).resolve("scripts/"+getDescriptor(module, ""));
+ return resolve(isShared?"shared":module.getName()+".data");
+ }
+
+ public String getDisplayPath()
+ {
+ return (isShared?"shared/":"")+(zipContainer!=null?zipContainer+"/":"")+resource+type.extension;
}
private String getDescriptor(Module module, String res)
@@ -136,7 +218,7 @@ public class FileArgument
{
return module.getName()+".data"+(res==null || res.isEmpty()?"":"/"+res);
}
- throw new RuntimeException("Invalid file descriptor: "+res);
+ throw new InternalExpressionException("Invalid file descriptor: "+res);
}
public Stream listFiles(Module module)
@@ -146,8 +228,7 @@ public class FileArgument
String ext = type.extension;
try
{
- return Files.list(dir).
- filter(path -> isFolder?Files.isDirectory(path):path.toString().endsWith(ext));
+ return Files.list(dir).filter(path -> (type==Type.FOLDER)?path.toString().endsWith("/"):path.toString().endsWith(ext));
}
catch (IOException ignored)
{
@@ -157,73 +238,106 @@ public class FileArgument
public Stream listFolder(Module module)
{
- Stream result;
- synchronized (writeIOSync) { result = listFiles(module); }
- if (result == null) return null;
- Path rootPath = moduleRootPath(module);
- Stream strings = result.map(p -> rootPath.relativize(p).toString().replaceAll("[\\\\/]","/"));
- if (type != Type.FOLDER)
- return strings.map(FilenameUtils::removeExtension);
- return strings;
+ Stream strings;
+ try { synchronized (writeIOSync)
+ {
+ Stream result;
+ result = listFiles(module);
+ if (result == null) return null;
+ Path rootPath = moduleRootPath(module);
+ if (rootPath == null) return null;
+ String zipComponent = (zipContainer != null) ? rootPath.relativize(zipPath).toString() : null;
+ strings = result.map(p -> {
+ if (zipContainer == null)
+ return rootPath.relativize(p).toString().replaceAll("[\\\\/]+", "/");
+ return (zipComponent + p.toString()).replaceAll("[\\\\/]+", "/");
+ });
+ } }
+ finally
+ {
+ close();
+ }
+ if (type == Type.FOLDER)
+ return strings.map(s -> s.endsWith("/")?s.substring(0, s.length()-2):s);
+ return strings.map(FilenameUtils::removeExtension);
+ }
+
+ private void createPaths(Path file)
+ {
+ try {
+ if ((zipContainer == null || file.getParent() != null) &&
+ !Files.exists(file.getParent()) &&
+ Files.createDirectories(file.getParent()) == null
+ ) throw new IOException();
+ } catch (IOException e) {
+ throw new ThrowStatement("Unable to create paths for "+file.toString(), Throwables.IO_EXCEPTION);
+ }
+
}
public boolean appendToTextFile(Module module, List message)
{
- Path dataFile = toPath(module);//, resourceName, supportedTypes.get(type), isShared);
- if (dataFile == null) return false;
- if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false;
- synchronized (writeIOSync)
+ try { synchronized (writeIOSync)
{
- try
+ Path dataFile = toPath(module);//, resourceName, supportedTypes.get(type), isShared);
+ if (dataFile == null) return false;
+ createPaths(dataFile);
+ OutputStream out = Files.newOutputStream(dataFile, StandardOpenOption.APPEND, StandardOpenOption.CREATE);
+ try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)))
{
- OutputStream out = Files.newOutputStream(dataFile, StandardOpenOption.APPEND, StandardOpenOption.CREATE);
- try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)))
+ for (String line: message)
{
- for (String line: message)
- {
- writer.append(line);
- if (type == Type.TEXT) writer.newLine();
- }
+ writer.append(line);
+ if (type == Type.TEXT) writer.newLine();
}
}
- catch (IOException e)
- {
- return false;
- }
- return true;
+ } }
+ catch (IOException e)
+ {
+ throw new ThrowStatement("Error when writing to the file: "+e, Throwables.IO_EXCEPTION);
}
+ finally
+ {
+ close();
+ }
+ return true;
}
public Tag getNbtData(Module module) // aka getData
{
- Path dataFile = toPath(module);
- if (dataFile == null) return null;
- if (!Files.exists(dataFile) || !(dataFile.toFile().isFile())) return null;
- synchronized (writeIOSync) { return readTag(dataFile.toFile()); }
+ try { synchronized (writeIOSync) {
+ Path dataFile = toPath(module);
+ if (dataFile == null || !Files.exists(dataFile)) return null;
+ return readTag(dataFile);
+ } }
+ finally {
+ close();
+ }
}
//copied private method from net.minecraft.nbt.NbtIo.read()
// to read non-compound tags - these won't be compressed
- public static Tag readTag(File file)
+ public static Tag readTag(Path path)
{
try
{
- return NbtIo.readCompressed(file);
+ return NbtIo.readCompressed(Files.newInputStream(path));
}
catch (IOException e)
{
// Copy of NbtIo.read(File) because that's now client-side only
- if (!file.exists())
+ if (!Files.exists(path))
{
return null;
}
- try (DataInputStream in = new DataInputStream(new FileInputStream(file)))
+ try (DataInputStream in = new DataInputStream(new BufferedInputStream(Files.newInputStream(path))))
{
return NbtIo.read(in);
}
catch (IOException ioException)
{
- try (DataInputStream dataInput_1 = new DataInputStream(new FileInputStream(file)))
+ // not compressed compound tag neither uncompressed compound tag - trying any type of a tag
+ try (DataInputStream dataInput_1 = new DataInputStream(new BufferedInputStream(Files.newInputStream(path))))
{
byte byte_1 = dataInput_1.readByte();
if (byte_1 == 0)
@@ -238,71 +352,102 @@ public class FileArgument
}
catch (IOException ignored)
{
+ throw new ThrowStatement("Not a valid NBT tag in "+path.toString(), Throwables.NBT_ERROR);
}
- return null;
}
}
+ catch (CrashException e)
+ {
+ throw new ThrowStatement("Error when reading NBT file "+path.toString(), Throwables.NBT_ERROR);
+ }
}
public boolean saveNbtData(Module module, Tag tag) // aka saveData
{
- Path dataFile = toPath(module);
- if (dataFile == null) return false;
- if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false;
- synchronized (writeIOSync) { return writeTag(tag, dataFile.toFile()); }
+ try { synchronized (writeIOSync) {
+ Path dataFile = toPath(module);
+ if (dataFile == null) return false;
+ createPaths(dataFile);
+ return writeTagDisk(tag, dataFile);
+ } }
+ finally
+ {
+ close();
+ }
}
//copied private method from net.minecraft.nbt.NbtIo.write() and client method safe_write
- public static boolean writeTag(Tag tag_1, File file)
+ public static boolean writeTagDisk(Tag tag_1, Path path)
{
- File file_2 = new File(file.getAbsolutePath() + "_tmp");
- if (file_2.exists()) file_2.delete();
+ Path original = path;
+ try
+ {
+ boolean zipped = path instanceof ZipPath;
+ if (!zipped)
+ {
+ path = new File(path.toFile().getAbsolutePath() + "_tmp").toPath();
+ Files.deleteIfExists(path);
+ }
- if (tag_1 instanceof CompoundTag)
- {
- try
+ if (tag_1 instanceof CompoundTag)
{
- NbtIo.writeCompressed((CompoundTag) tag_1, file_2);
+ NbtIo.writeCompressed((CompoundTag) tag_1, Files.newOutputStream(path));
}
- catch (IOException e)
+ else
{
- return false;
- }
- }
- else
- {
- try (DataOutputStream dataOutputStream_1 = new DataOutputStream(new FileOutputStream(file_2)))
- {
- dataOutputStream_1.writeByte(tag_1.getType());
- if (tag_1.getType() != 0)
+ try (DataOutputStream dataOutputStream_1 = new DataOutputStream(Files.newOutputStream(path)))
{
- dataOutputStream_1.writeUTF("");
- tag_1.write(dataOutputStream_1);
+ dataOutputStream_1.writeByte(tag_1.getType());
+ if (tag_1.getType() != 0)
+ {
+ dataOutputStream_1.writeUTF("");
+ tag_1.write(dataOutputStream_1);
+ }
}
}
- catch (IOException e)
+ if (!zipped)
{
- return false;
+ Files.deleteIfExists(original);
+ Files.move(path, original);
}
+ return true;
+ }
+ catch (IOException e)
+ {
+ throw new ThrowStatement("Unable to write tag to "+original.toString(), Throwables.IO_EXCEPTION);
}
- if (file.exists()) file.delete();
- if (!file.exists()) file_2.renameTo(file);
- return true;
}
public boolean dropExistingFile(Module module)
{
- Path dataFile = toPath(module);
- if (dataFile == null) return false;
- synchronized (writeIOSync) { return dataFile.toFile().delete(); }
+
+ try { synchronized (writeIOSync)
+ {
+ Path dataFile = toPath(module);
+ if (dataFile == null) return false;
+ return Files.deleteIfExists(dataFile);
+ } }
+ catch (IOException e)
+ {
+ throw new ThrowStatement("Error while removing file: "+getDisplayPath(), Throwables.IO_EXCEPTION);
+ }
+ finally
+ {
+ close();
+ }
}
public List listFile(Module module)
{
- Path dataFile = toPath(module);
- if (dataFile == null) return null;
- if (!dataFile.toFile().exists()) return null;
- synchronized (writeIOSync) { return listFileContent(dataFile); }
+ try { synchronized (writeIOSync) {
+ Path dataFile = toPath(module);
+ if (dataFile == null) return null;
+ if (!Files.exists(dataFile)) return null;
+ return listFileContent(dataFile);
+ } }
+ finally {
+ close();
+ }
}
public static List listFileContent(Path filePath)
@@ -319,15 +464,19 @@ public class FileArgument
}
catch (IOException e)
{
- return null;
+ throw new ThrowStatement("Failed to read text file "+filePath.toString(), Throwables.IO_EXCEPTION);
}
}
public JsonElement readJsonFile(Module module) {
- Path dataFile = toPath(module);
- if (dataFile == null) return null;
- if (!dataFile.toFile().exists()) return null;
- synchronized (writeIOSync) { return readJsonContent(dataFile); }
+ try { synchronized (writeIOSync) {
+ Path dataFile = toPath(module);
+ if (dataFile == null || !Files.exists(dataFile)) return null;
+ return readJsonContent(dataFile);
+ } }
+ finally {
+ close();
+ }
}
public static JsonElement readJsonContent(Path filePath)
@@ -336,9 +485,19 @@ public class FileArgument
{
return new JsonParser().parse(new JsonReader(reader));
}
+ catch (JsonParseException e)
+ {
+ Throwable exc = e;
+ if(e.getCause() != null)
+ exc = e.getCause();
+ throw new ThrowStatement(MapValue.wrap(ImmutableMap.of(
+ StringValue.of("error"), StringValue.of(exc.getMessage()),
+ StringValue.of("path"), StringValue.of(filePath.toString())
+ )), Throwables.JSON_ERROR);
+ }
catch (IOException e)
{
- return null;
+ throw new ThrowStatement("Failed to read json file content "+filePath.toString(), Throwables.IO_EXCEPTION);
}
}
diff --git a/src/main/java/carpet/script/bundled/FileModule.java b/src/main/java/carpet/script/bundled/FileModule.java
index 20cd65af..23d558f5 100644
--- a/src/main/java/carpet/script/bundled/FileModule.java
+++ b/src/main/java/carpet/script/bundled/FileModule.java
@@ -1,33 +1,10 @@
package carpet.script.bundled;
-import net.minecraft.nbt.CompoundTag;
-import net.minecraft.nbt.NbtIo;
-import net.minecraft.nbt.PositionTracker;
-import net.minecraft.nbt.Tag;
-import net.minecraft.nbt.TagReaders;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Locale;
-import java.util.stream.Stream;
-
-import com.google.gson.JsonElement;
-import com.google.gson.JsonParser;
-import com.google.gson.stream.JsonReader;
public class FileModule extends Module
{
@@ -66,155 +43,4 @@ public class FileModule extends Module
return library;
}
- //copied private method from net.minecraft.nbt.NbtIo.read()
- // to read non-compound tags - these won't be compressed
- @Deprecated
- public static Tag read(File file)
- {
- try
- {
- return NbtIo.readCompressed(file);
- }
- catch (IOException e)
- {
- // Copy of NbtIo.read(File) because that's now client-side only
- if (!file.exists())
- {
- return null;
- }
- try (DataInputStream in = new DataInputStream(new FileInputStream(file)))
- {
- return NbtIo.read(in);
- }
- catch (IOException ioException)
- {
- try (DataInputStream dataInput_1 = new DataInputStream(new FileInputStream(file)))
- {
- byte byte_1 = dataInput_1.readByte();
- if (byte_1 == 0)
- {
- return null;
- }
- else
- {
- dataInput_1.readUTF();
- return TagReaders.of(byte_1).read(dataInput_1, 0, PositionTracker.DEFAULT);
- }
- }
- catch (IOException ignored)
- {
- }
- return null;
- }
- }
- }
-
- //copied private method from net.minecraft.nbt.NbtIo.write() and client method safe_write
- @Deprecated
- public static boolean write(Tag tag_1, File file)
- {
- File file_2 = new File(file.getAbsolutePath() + "_tmp");
- if (file_2.exists()) file_2.delete();
-
- if (tag_1 instanceof CompoundTag)
- {
- try
- {
- NbtIo.writeCompressed((CompoundTag) tag_1, file_2);
- }
- catch (IOException e)
- {
- return false;
- }
- }
- else
- {
- try (DataOutputStream dataOutputStream_1 = new DataOutputStream(new FileOutputStream(file_2)))
- {
- dataOutputStream_1.writeByte(tag_1.getType());
- if (tag_1.getType() != 0)
- {
- dataOutputStream_1.writeUTF("");
- tag_1.write(dataOutputStream_1);
- }
- }
- catch (IOException e)
- {
- return false;
- }
- }
- if (file.exists()) file.delete();
- if (!file.exists()) file_2.renameTo(file);
- return true;
- }
-
- @Deprecated
- public static boolean appendText(Path filePath, boolean addNewLines, List data)
- {
- try
- {
- OutputStream out = Files.newOutputStream(filePath, StandardOpenOption.APPEND, StandardOpenOption.CREATE);
- try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)))
- {
- for (String line: data)
- {
- writer.append(line);
- if (addNewLines) writer.newLine();
- }
- }
- }
- catch (IOException e)
- {
- return false;
- }
- return true;
- }
-
-
- @Deprecated
- public static List listFileContent(Path filePath)
- {
- try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) {
- List result = new ArrayList<>();
- for (;;) {
- String line = reader.readLine();
- if (line == null)
- break;
- result.add(line.replaceAll("[\n\r]+",""));
- }
- return result;
- }
- catch (IOException e)
- {
- return null;
- }
- }
-
- @Deprecated
- public static JsonElement readJsonContent(Path filePath)
- {
- try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8))
- {
- return new JsonParser().parse(new JsonReader(reader));
- }
- catch (IOException e)
- {
- return null;
- }
- }
-
- @Deprecated
- public static Stream listFiles(Path dir, String ext)
- {
- boolean folderListing = ext.equals("folder");
- try
- {
- return Files.list(dir).
- filter(path -> folderListing?Files.isDirectory(path):path.toString().endsWith(ext));
- }
- catch (IOException ignored)
- {
- return null;
- }
- }
}
diff --git a/src/main/java/carpet/script/bundled/Module.java b/src/main/java/carpet/script/bundled/Module.java
index ea6231b5..f76c4ad8 100644
--- a/src/main/java/carpet/script/bundled/Module.java
+++ b/src/main/java/carpet/script/bundled/Module.java
@@ -1,123 +1,39 @@
package carpet.script.bundled;
import carpet.CarpetServer;
+import carpet.script.argument.FileArgument;
import net.minecraft.nbt.Tag;
import net.minecraft.util.WorldSavePath;
-import org.apache.commons.io.FilenameUtils;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.gson.JsonElement;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Stream;
public abstract class Module
{
public abstract String getName();
public abstract String getCode();
public abstract boolean isLibrary();
- @Deprecated
- public final static Object writeIOSync = new Object();
- @Deprecated
- public final static Map supportedTypes = ImmutableMap.of(
- "raw", ".txt",
- "text", ".txt",
- "nbt", ".nbt",
- "json", ".json",
- "folder", "folder"
- );
- @Deprecated
- private static String getDescriptor(Module module, String file, boolean isShared)
+ public static Tag getData(Module module)
{
- if (isShared)
- {
- return file.isEmpty()?"shared":"shared/"+file;
- }
- if (module != null && module.getName() != null) // appdata
- {
- return module.getName()+".data"+(file==null || file.isEmpty()?"":"/"+file);
- }
- throw new RuntimeException("Invalid file descriptor: "+file);
- }
-
- @Deprecated
- public static Tag getData(Module module, String file, boolean isShared)
- {
- Path dataFile = resolveResource(module, file, ".nbt", isShared);
+ Path dataFile = resolveResource(module);
if (dataFile == null) return null;
if (!Files.exists(dataFile) || !(dataFile.toFile().isFile())) return null;
- synchronized (writeIOSync) { return FileModule.read(dataFile.toFile()); }
+ synchronized (FileArgument.writeIOSync) { return FileArgument.readTag(dataFile); }
}
- @Deprecated
- public static boolean saveData(Module module, String file, Tag globalState, boolean isShared)
+ public static void saveData(Module module, Tag globalState)
{
- Path dataFile = resolveResource(module, file, ".nbt", isShared);
- if (dataFile == null) return false;
- if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false;
- synchronized (writeIOSync) { return FileModule.write(globalState, dataFile.toFile()); }
+ Path dataFile = resolveResource(module);
+ if (dataFile == null) return;
+ if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return;
+ synchronized (FileArgument.writeIOSync) { FileArgument.writeTagDisk(globalState, dataFile); }
}
- @Deprecated
- public static boolean appendToTextFile(Module module, String resourceName, String type, boolean isShared, List message)
+ private static Path resolveResource(Module module)
{
- Path dataFile = resolveResource(module, resourceName, supportedTypes.get(type), isShared);
- if (dataFile == null) return false;
- if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false;
- synchronized (writeIOSync) { return FileModule.appendText(dataFile, type.equals("text"), message); }
- }
-
- @Deprecated
- public static boolean dropExistingFile(Module module, String resourceName, String type, boolean isShared)
- {
- Path dataFile = resolveResource(module, resourceName, supportedTypes.get(type), isShared);
- if (dataFile == null) return false;
- synchronized (writeIOSync) { return dataFile.toFile().delete(); }
- }
-
- @Deprecated
- public static List listFile(Module module, String resourceName, String type, boolean isShared)
- {
- Path dataFile = resolveResource(module, resourceName, supportedTypes.get(type), isShared);
- if (dataFile == null) return null;
- if (!dataFile.toFile().exists()) return null;
- synchronized (writeIOSync) { return FileModule.listFileContent(dataFile); }
- }
-
- @Deprecated
- public static JsonElement readJsonFile(Module module, String resourceName, String type, boolean isShared) {
- Path dataFile = resolveResource(module, resourceName, supportedTypes.get(type), isShared);
- if (dataFile == null) return null;
- if (!dataFile.toFile().exists()) return null;
- synchronized (writeIOSync) { return FileModule.readJsonContent(dataFile); }
- }
-
- @Deprecated
- public static Stream listFolder(Module module, String resourceName, String type, boolean isShared)
- {
- Path dir = resolveResource(module, resourceName, null, isShared);
- String ext = supportedTypes.get(type);
- if (dir == null) return null;
- if (!Files.exists(dir)) return null;
- Stream result;
- synchronized (writeIOSync) { result = FileModule.listFiles(dir, ext); }
- if (result == null) return null;
- Path rootPath = resolveResource(module, "", null, isShared);
- Stream strings = result.map(p -> rootPath.relativize(p).toString().replaceAll("[\\\\/]","/"));
- if (!ext.equals("folder"))
- return strings.map(FilenameUtils::removeExtension);
- return strings;
- }
-
- @Deprecated
- private static Path resolveResource(Module module, String resourceName, String ext, boolean isShared)
- {
- if (!isShared && (module == null || module.getName() == null)) return null;
- return CarpetServer.minecraft_server.getSavePath(WorldSavePath.ROOT).resolve("scripts/"+getDescriptor(module, resourceName, isShared)+(ext==null?"":ext));
+ if (module == null || module.getName() == null) return null; // commandline app
+ return CarpetServer.minecraft_server.getSavePath(WorldSavePath.ROOT).resolve("scripts/"+module.getName()+".data.nbt");
}
@Override
diff --git a/src/main/java/carpet/script/exception/Throwables.java b/src/main/java/carpet/script/exception/Throwables.java
index 22857a41..3f811cfe 100644
--- a/src/main/java/carpet/script/exception/Throwables.java
+++ b/src/main/java/carpet/script/exception/Throwables.java
@@ -25,8 +25,8 @@ public class Throwables {
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 NBT_ERROR = register("nbt_error", IO_EXCEPTION);
+ public static final Throwables JSON_ERROR = register("json_error", IO_EXCEPTION);
public static final Throwables USER_DEFINED = register("user_exception", THROWN_EXCEPTION_TYPE);
/**
diff --git a/src/main/java/carpet/script/language/Loops.java b/src/main/java/carpet/script/language/Loops.java
index 4908a853..ad041cd2 100644
--- a/src/main/java/carpet/script/language/Loops.java
+++ b/src/main/java/carpet/script/language/Loops.java
@@ -119,7 +119,7 @@ public class Loops {
c.setVariable("_i", (cc, tt) -> new NumericValue(doYouReally).bindTo("_i"));
try
{
- result.add(expr.evalValue(c));
+ result.add(expr.evalValue(c, t));
}
catch (BreakStatement | ContinueStatement stmt)
{