diff --git a/docs/scarpet/api/Overview.md b/docs/scarpet/api/Overview.md index a92da2af..e3ba6e6b 100644 --- a/docs/scarpet/api/Overview.md +++ b/docs/scarpet/api/Overview.md @@ -40,9 +40,13 @@ apps `__on_close()` will execute once per app, and with `'player'` scoped apps, ### App config via `__config()` function If an app defines `__config` method, and that method returns a map, it will be used to apply custom settings -for this app. Currently the following options are supported: +for this app. Currently, the following options are supported: -* `'scope'`: default scope for global variables for the app, Default is `'player'`, which means that globals and defined +* `'strict'` : if `true`, any use of an uninitialized variable will result in program failure. Defaults to `false` if +not specified. With `'strict'`you have to assign an initial value to any variable before using it. It is very useful +to use this setting for app debugging and for beginner programmers. Explicit initialization is not required for your +code to work, but mistakes may result from improper assumptions about initial variable values of `null`. +* `'scope'`: default scope for global variables for the app, Default is `'player'`, which means that globals and defined functions will be unique for each player so that apps for each player will run in isolation. This is useful in tool-like applications, where behaviour of things is always from a player's perspective. With player scope the initial run of the app creates is initial state: defined functions, global variables, config and event handlers, which is then copied for diff --git a/src/main/java/carpet/script/CarpetScriptHost.java b/src/main/java/carpet/script/CarpetScriptHost.java index b8049b77..0f515a33 100644 --- a/src/main/java/carpet/script/CarpetScriptHost.java +++ b/src/main/java/carpet/script/CarpetScriptHost.java @@ -100,6 +100,7 @@ public class CarpetScriptHost extends ScriptHost else if (parent != null) { persistenceRequired = ((CarpetScriptHost)parent).persistenceRequired; + strict = parent.strict; } appConfig = config; appArgTypes = argTypes; @@ -306,6 +307,7 @@ public class CarpetScriptHost extends ScriptHost Map config = ((MapValue) ret).getMap(); setPerPlayer(config.getOrDefault(new StringValue("scope"), new StringValue("player")).getString().equalsIgnoreCase("player")); persistenceRequired = config.getOrDefault(new StringValue("stay_loaded"), Value.TRUE).getBoolean(); + strict = config.getOrDefault(StringValue.of("strict"), Value.FALSE).getBoolean(); // check requires Value loadRequirements = config.get(new StringValue("requires")); if (loadRequirements instanceof FunctionValue) diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java index 4796765c..7ba73179 100644 --- a/src/main/java/carpet/script/Expression.java +++ b/src/main/java/carpet/script/Expression.java @@ -738,7 +738,7 @@ public class Expression } var = c.host.getGlobalVariable(module, name); if (var != null) return var; - var = (_c, _t ) -> Value.ZERO.reboundedTo(name); + var = (_c, _t ) -> _c.host.strict ? Value.UNDEF.reboundedTo(name) : Value.NULL.reboundedTo(name); setAnyVariable(c, name, var); return var; } diff --git a/src/main/java/carpet/script/ScriptHost.java b/src/main/java/carpet/script/ScriptHost.java index 9da1e036..56216c72 100644 --- a/src/main/java/carpet/script/ScriptHost.java +++ b/src/main/java/carpet/script/ScriptHost.java @@ -36,6 +36,7 @@ public abstract class ScriptHost private final Map executorServices = new HashMap<>(); private final Map locks = new ConcurrentHashMap<>(); protected boolean inTermination = false; + public boolean strict; private final Set deprecations = new HashSet<>(); @@ -118,6 +119,7 @@ public abstract class ScriptHost this.main = code; this.perUser = perUser; this.user = null; + this.strict = false; ModuleData moduleData = new ModuleData(code); initializeModuleGlobals(moduleData); this.moduleData.put(code, moduleData); diff --git a/src/main/java/carpet/script/value/NullValue.java b/src/main/java/carpet/script/value/NullValue.java index 23bba606..8e813f8b 100644 --- a/src/main/java/carpet/script/value/NullValue.java +++ b/src/main/java/carpet/script/value/NullValue.java @@ -32,7 +32,7 @@ public class NullValue extends NumericValue // TODO check nonsingleton code { return new NullValue(); } - private NullValue() {super(0);} + protected NullValue() {super(0);} @Override public boolean equals(final Object o) diff --git a/src/main/java/carpet/script/value/UndefValue.java b/src/main/java/carpet/script/value/UndefValue.java new file mode 100644 index 00000000..89c35c97 --- /dev/null +++ b/src/main/java/carpet/script/value/UndefValue.java @@ -0,0 +1,140 @@ +package carpet.script.value; + +import carpet.script.exception.InternalExpressionException; +import com.google.gson.JsonElement; +import net.minecraft.nbt.Tag; + +public class UndefValue extends NullValue { + public static final UndefValue UNDEF = new UndefValue(); + + private RuntimeException getError() { + return new InternalExpressionException("variable "+boundVariable+" was used before initialization under 'strict' app config"); + } + + + @Override + public String getString() + { + throw getError(); + } + + @Override + public String getPrettyString() + { + return "undefined"; + } + + @Override + public boolean getBoolean() + { + throw getError(); + } + + @Override + public Value clone() + { + return new UndefValue(); + } + + @Override + public boolean equals(final Object o) + { + throw getError(); + } + + @Override + public Value slice(long fromDesc, Long toDesc) { + throw getError(); + } + + @Override + public NumericValue opposite() { + throw getError(); + } + + @Override + public int length() { + throw getError(); + } + + @Override + public int compareTo(Value o) + { + throw getError(); + } + + @Override + public Value in(Value value) { + throw getError(); + } + + @Override + public String getTypeString() + { + return "undef"; + } + + @Override + public int hashCode() + { + throw getError(); + } + + @Override + public Tag toTag(boolean force) + { + throw getError(); + } + + @Override + public Value split(Value delimiter) { + throw getError(); + } + + @Override + public JsonElement toJson() + { + throw getError(); + } + + @Override + public boolean isNull() { + throw getError(); + } + + @Override + public Value add(Value v) { + throw getError(); + } + + @Override + public Value subtract(Value v) + { + throw getError(); + } + + @Override + public Value multiply(Value v) + { + throw getError(); + } + + @Override + public Value divide(Value v) + { + throw getError(); + } + + @Override + public double readDoubleNumber() + { + throw getError(); + } + + @Override + public long readInteger() + { + throw getError(); + } + +} diff --git a/src/main/java/carpet/script/value/Value.java b/src/main/java/carpet/script/value/Value.java index f2919f47..c28edd07 100644 --- a/src/main/java/carpet/script/value/Value.java +++ b/src/main/java/carpet/script/value/Value.java @@ -21,6 +21,7 @@ public abstract class Value implements Comparable, Cloneable public static NumericValue ONE = new NumericValue(1); public static NullValue NULL = NullValue.NULL; + public static UndefValue UNDEF = UndefValue.UNDEF; public String boundVariable;