From b97634b7177e4855f56de44f80a882313654866a Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Sat, 8 Mar 2025 23:39:11 +0000 Subject: [PATCH] Flesh out LuaTable a bit Add a whole buncha helper methods for parsing values, much like IArguments. This allows us to remove TableHelper. Gosh, that dates back to 2018! --- .../computercraft/api/lua/IArguments.java | 3 + .../computercraft/api/lua/LuaTable.java | 379 +++++++++++++++++- .../computercraft/api/lua/LuaValues.java | 14 +- .../computercraft/core/apis/HTTPAPI.java | 26 +- .../computercraft/core/apis/TableHelper.java | 156 ------- 5 files changed, 394 insertions(+), 184 deletions(-) delete mode 100644 projects/core/src/main/java/dan200/computercraft/core/apis/TableHelper.java diff --git a/projects/core-api/src/main/java/dan200/computercraft/api/lua/IArguments.java b/projects/core-api/src/main/java/dan200/computercraft/api/lua/IArguments.java index e70527785..f554b05e5 100644 --- a/projects/core-api/src/main/java/dan200/computercraft/api/lua/IArguments.java +++ b/projects/core-api/src/main/java/dan200/computercraft/api/lua/IArguments.java @@ -223,6 +223,9 @@ public interface IArguments { /** * Get an argument as a table. + *

+ * The returned table may be converted into a {@link LuaTable} (using {@link ObjectLuaTable}) for easier parsing of + * table keys. * * @param index The argument number. * @return The argument's value. diff --git a/projects/core-api/src/main/java/dan200/computercraft/api/lua/LuaTable.java b/projects/core-api/src/main/java/dan200/computercraft/api/lua/LuaTable.java index 006f6f51d..fa42b1395 100644 --- a/projects/core-api/src/main/java/dan200/computercraft/api/lua/LuaTable.java +++ b/projects/core-api/src/main/java/dan200/computercraft/api/lua/LuaTable.java @@ -7,15 +7,18 @@ package dan200.computercraft.api.lua; import org.jspecify.annotations.Nullable; import java.util.Map; +import java.util.Optional; import static dan200.computercraft.api.lua.LuaValues.*; /** - * A view of a Lua table, which may be able to access table elements in a more optimised manner than - * {@link IArguments#getTable(int)}. + * A view of a Lua table. + *

+ * Much like {@link IArguments}, this allows for convenient parsing of fields from a Lua table. * * @param The type of keys in a table, will typically be a wildcard. * @param The type of values in a table, will typically be a wildcard. + * @see ObjectArguments */ public interface LuaTable extends Map { /** @@ -29,19 +32,47 @@ public interface LuaTable extends Map { return size; } + /** + * Get an array entry as a double. + * + * @param index The index in the table, starting at 1. + * @return The entry's value. + * @throws LuaException If the value is not a number. + * @see #getFiniteDouble(int) if you require this to be finite (i.e. not infinite or NaN). + * @since 1.116 + */ + default double getDouble(int index) throws LuaException { + Object value = get((double) index); + if (!(value instanceof Number number)) throw badTableItem(index, "number", getType(value)); + return number.doubleValue(); + } + + /** + * Get a table entry as a double. + * + * @param key The name of the field in the table. + * @return The field's value. + * @throws LuaException If the value is not a number. + * @see #getFiniteDouble(String) if you require this to be finite (i.e. not infinite or NaN). + * @since 1.116 + */ + default double getDouble(String key) throws LuaException { + Object value = get(key); + if (!(value instanceof Number number)) throw badField(key, "number", getType(value)); + return number.doubleValue(); + } + /** * Get an array entry as an integer. * * @param index The index in the table, starting at 1. - * @return The table's value. + * @return The entry's value. * @throws LuaException If the value is not an integer. */ default long getLong(int index) throws LuaException { Object value = get((double) index); if (!(value instanceof Number number)) throw badTableItem(index, "number", getType(value)); - - var asDouble = number.doubleValue(); - if (!Double.isFinite(asDouble)) throw badTableItem(index, "number", getNumericType(asDouble)); + checkFiniteIndex(index, number.doubleValue()); return number.longValue(); } @@ -49,15 +80,13 @@ public interface LuaTable extends Map { * Get a table entry as an integer. * * @param key The name of the field in the table. - * @return The table's value. + * @return The field's value. * @throws LuaException If the value is not an integer. */ default long getLong(String key) throws LuaException { Object value = get(key); if (!(value instanceof Number number)) throw badField(key, "number", getType(value)); - - var asDouble = number.doubleValue(); - if (!Double.isFinite(asDouble)) throw badField(key, "number", getNumericType(asDouble)); + checkFiniteField(key, number.doubleValue()); return number.longValue(); } @@ -65,7 +94,7 @@ public interface LuaTable extends Map { * Get an array entry as an integer. * * @param index The index in the table, starting at 1. - * @return The table's value. + * @return The entry's value. * @throws LuaException If the value is not an integer. */ default int getInt(int index) throws LuaException { @@ -76,13 +105,339 @@ public interface LuaTable extends Map { * Get a table entry as an integer. * * @param key The name of the field in the table. - * @return The table's value. + * @return The field's value. * @throws LuaException If the value is not an integer. */ default int getInt(String key) throws LuaException { return (int) getLong(key); } + /** + * Get an argument as a finite number (not infinite or NaN). + * + * @param index The index in the table, starting at 1. + * @return The entry's value. + * @throws LuaException If the value is not finite. + * @since 1.116 + */ + default double getFiniteDouble(int index) throws LuaException { + return checkFiniteIndex(index, getDouble(index)); + } + + /** + * Get an argument as a finite number (not infinite or NaN). + * + * @param key The name of the field in the table. + * @return The field's value. + * @throws LuaException If the value is not finite. + * @since 1.116 + */ + default double getFiniteDouble(String key) throws LuaException { + return checkFiniteField(key, getDouble(key)); + } + + /** + * Get an array entry as a boolean. + * + * @param index The index in the table, starting at 1. + * @return The entry's value. + * @throws LuaException If the value is not a boolean. + * @since 1.116 + */ + default boolean getBoolean(int index) throws LuaException { + Object value = get((double) index); + if (!(value instanceof Boolean bool)) throw badTableItem(index, "boolean", getType(value)); + return bool; + } + + /** + * Get a table entry as a boolean. + * + * @param key The name of the field in the table. + * @return The field's value. + * @throws LuaException If the value is not a boolean. + * @since 1.116 + */ + default boolean getBoolean(String key) throws LuaException { + Object value = get(key); + if (!(value instanceof Boolean bool)) throw badField(key, "boolean", getType(value)); + return bool; + } + + /** + * Get an array entry as a string. + * + * @param index The index in the table, starting at 1. + * @return The entry's value. + * @throws LuaException If the value is not a string. + * @since 1.116 + */ + default String getString(int index) throws LuaException { + Object value = get((double) index); + if (!(value instanceof String string)) throw badTableItem(index, "string", getType(value)); + return string; + } + + /** + * Get a table entry as a string. + * + * @param key The name of the field in the table. + * @return The field's value. + * @throws LuaException If the value is not a string. + * @since 1.116 + */ + default String getString(String key) throws LuaException { + Object value = get(key); + if (!(value instanceof String string)) throw badField(key, "string", getType(value)); + return string; + } + + /** + * Get an array entry as a table. + *

+ * The returned table may be converted into a {@link LuaTable} (using {@link ObjectLuaTable}) for easier parsing of + * table keys. + * + * @param index The index in the table, starting at 1. + * @return The entry's value. + * @throws LuaException If the value is not a table. + * @since 1.116 + */ + default Map getTable(int index) throws LuaException { + Object value = get((double) index); + if (!(value instanceof Map table)) throw badTableItem(index, "table", getType(value)); + return table; + } + + /** + * Get a table entry as a table. + *

+ * The returned table may be converted into a {@link LuaTable} (using {@link ObjectLuaTable}) for easier parsing of + * table keys. + * + * @param key The name of the field in the table. + * @return The field's value. + * @throws LuaException If the value is not a table. + * @since 1.116 + */ + default Map getTable(String key) throws LuaException { + Object value = get(key); + if (!(value instanceof Map table)) throw badField(key, "table", getType(value)); + return table; + } + + /** + * Get an array entry as a double. + * + * @param index The index in the table, starting at 1. + * @return The entry's value, or {@link Optional#empty()} if not present. + * @throws LuaException If the value is not a number. + * @see #getFiniteDouble(int) if you require this to be finite (i.e. not infinite or NaN). + * @since 1.116 + */ + default Optional optDouble(int index) throws LuaException { + Object value = get((double) index); + if (value == null) return Optional.empty(); + if (!(value instanceof Number number)) throw badTableItem(index, "number", getType(value)); + return Optional.of(number.doubleValue()); + } + + /** + * Get a table entry as a double. + * + * @param key The name of the field in the table. + * @return The field's value, or {@link Optional#empty()} if not present. + * @throws LuaException If the value is not a number. + * @see #getFiniteDouble(String) if you require this to be finite (i.e. not infinite or NaN). + * @since 1.116 + */ + default Optional optDouble(String key) throws LuaException { + Object value = get(key); + if (value == null) return Optional.empty(); + if (!(value instanceof Number number)) throw badField(key, "number", getType(value)); + return Optional.of(number.doubleValue()); + } + + /** + * Get an array entry as an integer. + * + * @param index The index in the table, starting at 1. + * @return The entry's value, or {@link Optional#empty()} if not present. + * @throws LuaException If the value is not an integer. + * @since 1.116 + */ + default Optional optLong(int index) throws LuaException { + Object value = get((double) index); + if (value == null) return Optional.empty(); + if (!(value instanceof Number number)) throw badTableItem(index, "number", getType(value)); + checkFiniteIndex(index, number.doubleValue()); + return Optional.of(number.longValue()); + } + + /** + * Get a table entry as an integer. + * + * @param key The name of the field in the table. + * @return The field's value, or {@link Optional#empty()} if not present. + * @throws LuaException If the value is not an integer. + * @since 1.116 + */ + default Optional optLong(String key) throws LuaException { + Object value = get(key); + if (value == null) return Optional.empty(); + if (!(value instanceof Number number)) throw badField(key, "number", getType(value)); + checkFiniteField(key, number.doubleValue()); + return Optional.of(number.longValue()); + } + + /** + * Get an array entry as an integer. + * + * @param index The index in the table, starting at 1. + * @return The entry's value, or {@link Optional#empty()} if not present. + * @throws LuaException If the value is not an integer. + * @since 1.116 + */ + default Optional optInt(int index) throws LuaException { + return optLong(index).map(Long::intValue); + } + + /** + * Get a table entry as an integer. + * + * @param key The name of the field in the table. + * @return The field's value, or {@link Optional#empty()} if not present. + * @throws LuaException If the value is not an integer. + * @since 1.116 + */ + default Optional optInt(String key) throws LuaException { + return optLong(key).map(Long::intValue); + } + + /** + * Get an argument as a finite number (not infinite or NaN). + * + * @param index The index in the table, starting at 1. + * @return The entry's value, or {@link Optional#empty()} if not present. + * @throws LuaException If the value is not finite. + * @since 1.116 + */ + default Optional optFiniteDouble(int index) throws LuaException { + var value = optDouble(index); + if (value.isPresent()) checkFiniteIndex(index, value.get()); + return value; + } + + /** + * Get an argument as a finite number (not infinite or NaN). + * + * @param key The name of the field in the table. + * @return The field's value, or {@link Optional#empty()} if not present. + * @throws LuaException If the value is not finite. + * @since 1.116 + */ + default Optional optFiniteDouble(String key) throws LuaException { + var value = optDouble(key); + if (value.isPresent()) checkFiniteField(key, value.get()); + return value; + } + + /** + * Get an array entry as a boolean. + * + * @param index The index in the table, starting at 1. + * @return The entry's value, or {@link Optional#empty()} if not present. + * @throws LuaException If the value is not a boolean. + * @since 1.116 + */ + default Optional optBoolean(int index) throws LuaException { + Object value = get((double) index); + if (value == null) return Optional.empty(); + if (!(value instanceof Boolean bool)) throw badTableItem(index, "boolean", getType(value)); + return Optional.of(bool); + } + + /** + * Get a table entry as a boolean. + * + * @param key The name of the field in the table. + * @return The field's value, or {@link Optional#empty()} if not present. + * @throws LuaException If the value is not a boolean. + * @since 1.116 + */ + default Optional optBoolean(String key) throws LuaException { + Object value = get(key); + if (value == null) return Optional.empty(); + if (!(value instanceof Boolean bool)) throw badField(key, "boolean", getType(value)); + return Optional.of(bool); + } + + /** + * Get an array entry as a double. + * + * @param index The index in the table, starting at 1. + * @return The entry's value, or {@link Optional#empty()} if not present. + * @throws LuaException If the value is not a string. + * @since 1.116 + */ + default Optional optString(int index) throws LuaException { + Object value = get((double) index); + if (value == null) return Optional.empty(); + if (!(value instanceof String string)) throw badTableItem(index, "string", getType(value)); + return Optional.of(string); + } + + /** + * Get a table entry as a string. + * + * @param key The name of the field in the table. + * @return The field's value, or {@link Optional#empty()} if not present. + * @throws LuaException If the value is not a string. + * @since 1.116 + */ + default Optional optString(String key) throws LuaException { + Object value = get(key); + if (value == null) return Optional.empty(); + if (!(value instanceof String string)) throw badField(key, "string", getType(value)); + return Optional.of(string); + } + + /** + * Get an array entry as a table. + *

+ * The returned table may be converted into a {@link LuaTable} (using {@link ObjectLuaTable}) for easier parsing of + * table keys. + * + * @param index The index in the table, starting at 1. + * @return The entry's value, or {@link Optional#empty()} if not present. + * @throws LuaException If the value is not a table. + * @since 1.116 + */ + default Optional> optTable(int index) throws LuaException { + Object value = get((double) index); + if (value == null) return Optional.empty(); + if (!(value instanceof Map table)) throw badTableItem(index, "table", getType(value)); + return Optional.of(table); + } + + /** + * Get a table entry as a table. + *

+ * The returned table may be converted into a {@link LuaTable} (using {@link ObjectLuaTable}) for easier parsing of + * table keys. + * + * @param key The name of the field in the table. + * @return The field's value, or {@link Optional#empty()} if not present. + * @throws LuaException If the value is not a table. + * @since 1.116 + */ + default Optional> optTable(String key) throws LuaException { + Object value = get(key); + if (value == null) return Optional.empty(); + if (!(value instanceof Map table)) throw badField(key, "table", getType(value)); + return Optional.of(table); + } + @Nullable @Override default V put(K o, V o2) { diff --git a/projects/core-api/src/main/java/dan200/computercraft/api/lua/LuaValues.java b/projects/core-api/src/main/java/dan200/computercraft/api/lua/LuaValues.java index 61ec3635e..523ba84e6 100644 --- a/projects/core-api/src/main/java/dan200/computercraft/api/lua/LuaValues.java +++ b/projects/core-api/src/main/java/dan200/computercraft/api/lua/LuaValues.java @@ -97,7 +97,7 @@ public final class LuaValues { * @return The constructed exception, which should be thrown immediately. */ public static LuaException badTableItem(int index, String expected, String actual) { - return new LuaException("table item #" + index + " is not " + expected + " (got " + actual + ")"); + return new LuaException("bad item #" + index + " (" + expected + " expected, got " + actual + ")"); } /** @@ -109,7 +109,7 @@ public final class LuaValues { * @return The constructed exception, which should be thrown immediately. */ public static LuaException badField(String key, String expected, String actual) { - return new LuaException("field " + key + " is not " + expected + " (got " + actual + ")"); + return new LuaException("bad field '" + key + "' (" + expected + " expected, got " + actual + ")"); } /** @@ -138,6 +138,16 @@ public final class LuaValues { return value; } + static double checkFiniteIndex(int index, double value) throws LuaException { + if (!Double.isFinite(value)) throw badTableItem(index, "number", getNumericType(value)); + return value; + } + + static double checkFiniteField(String key, double value) throws LuaException { + if (!Double.isFinite(value)) throw badField(key, "number", getNumericType(value)); + return value; + } + /** * Ensure a string is a valid enum value. * diff --git a/projects/core/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java b/projects/core/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java index 4e198b4cc..3cce0fafa 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java +++ b/projects/core/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java @@ -20,7 +20,6 @@ import java.util.Locale; import java.util.Map; import java.util.Optional; -import static dan200.computercraft.core.apis.TableHelper.*; import static dan200.computercraft.core.util.ArgumentHelpers.assertBetween; /** @@ -78,15 +77,14 @@ public class HTTPAPI implements ILuaAPI { Optional timeoutArg; if (args.get(0) instanceof Map) { - var options = args.getTable(0); - address = getStringField(options, "url"); - var postString = optStringField(options, "body", null); - postBody = postString == null ? null : LuaValues.encode(postString); - headerTable = optTableField(options, "headers", Map.of()); - binary = optBooleanField(options, "binary", false); - requestMethod = optStringField(options, "method", null); - redirect = optBooleanField(options, "redirect", true); - timeoutArg = optRealField(options, "timeout"); + var options = new ObjectLuaTable(args.getTable(0)); + address = options.getString("url"); + postBody = options.optString("body").map(LuaValues::encode).orElse(null); + headerTable = options.optTable("headers").orElse(Map.of()); + binary = options.optBoolean("binary").orElse(false); + requestMethod = options.optString("method").orElse(null); + redirect = options.optBoolean("redirect").orElse(true); + timeoutArg = options.optFiniteDouble("timeout"); } else { // Get URL and post information address = args.getString(0); @@ -151,10 +149,10 @@ public class HTTPAPI implements ILuaAPI { Optional timeoutArg; if (args.get(0) instanceof Map) { - var options = args.getTable(0); - address = getStringField(options, "url"); - headerTable = optTableField(options, "headers", Map.of()); - timeoutArg = optRealField(options, "timeout"); + var options = new ObjectLuaTable(args.getTableUnsafe(0)); + address = options.getString("url"); + headerTable = options.optTable("headers").orElse(Map.of()); + timeoutArg = options.optFiniteDouble("timeout"); } else { address = args.getString(0); headerTable = args.optTable(1, Map.of()); diff --git a/projects/core/src/main/java/dan200/computercraft/core/apis/TableHelper.java b/projects/core/src/main/java/dan200/computercraft/core/apis/TableHelper.java deleted file mode 100644 index 9ccb5f39e..000000000 --- a/projects/core/src/main/java/dan200/computercraft/core/apis/TableHelper.java +++ /dev/null @@ -1,156 +0,0 @@ -// SPDX-FileCopyrightText: 2018 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.core.apis; - -import dan200.computercraft.api.lua.LuaException; -import dan200.computercraft.api.lua.LuaValues; -import org.jspecify.annotations.Nullable; - -import java.util.Map; -import java.util.Optional; - -import static dan200.computercraft.api.lua.LuaValues.getNumericType; - -/** - * Various helpers for tables. - */ -public final class TableHelper { - private TableHelper() { - throw new IllegalStateException("Cannot instantiate singleton " + getClass().getName()); - } - - public static LuaException badKey(String key, String expected, @Nullable Object actual) { - return badKey(key, expected, LuaValues.getType(actual)); - } - - public static LuaException badKey(String key, String expected, String actual) { - return new LuaException("bad field '" + key + "' (" + expected + " expected, got " + actual + ")"); - } - - public static double getNumberField(Map table, String key) throws LuaException { - var value = table.get(key); - if (value instanceof Number) { - return ((Number) value).doubleValue(); - } else { - throw badKey(key, "number", value); - } - } - - public static int getIntField(Map table, String key) throws LuaException { - var value = table.get(key); - if (value instanceof Number) { - return (int) ((Number) value).longValue(); - } else { - throw badKey(key, "number", value); - } - } - - public static double getRealField(Map table, String key) throws LuaException { - return checkReal(key, getNumberField(table, key)); - } - - public static boolean getBooleanField(Map table, String key) throws LuaException { - var value = table.get(key); - if (value instanceof Boolean) { - return (Boolean) value; - } else { - throw badKey(key, "boolean", value); - } - } - - public static String getStringField(Map table, String key) throws LuaException { - var value = table.get(key); - if (value instanceof String) { - return (String) value; - } else { - throw badKey(key, "string", value); - } - } - - @SuppressWarnings("unchecked") - public static Map getTableField(Map table, String key) throws LuaException { - var value = table.get(key); - if (value instanceof Map) { - return (Map) value; - } else { - throw badKey(key, "table", value); - } - } - - public static double optNumberField(Map table, String key, double def) throws LuaException { - var value = table.get(key); - if (value == null) { - return def; - } else if (value instanceof Number) { - return ((Number) value).doubleValue(); - } else { - throw badKey(key, "number", value); - } - } - - public static int optIntField(Map table, String key, int def) throws LuaException { - var value = table.get(key); - if (value == null) { - return def; - } else if (value instanceof Number) { - return (int) ((Number) value).longValue(); - } else { - throw badKey(key, "number", value); - } - } - - public static Optional optRealField(Map table, String key) throws LuaException { - var value = table.get(key); - if (value == null) { - return Optional.empty(); - } else { - return Optional.of(getRealField(table, key)); - } - } - - public static double optRealField(Map table, String key, double def) throws LuaException { - return checkReal(key, optNumberField(table, key, def)); - } - - public static boolean optBooleanField(Map table, String key, boolean def) throws LuaException { - var value = table.get(key); - if (value == null) { - return def; - } else if (value instanceof Boolean) { - return (Boolean) value; - } else { - throw badKey(key, "boolean", value); - } - } - - @Nullable - public static String optStringField(Map table, String key, @Nullable String def) throws LuaException { - var value = table.get(key); - if (value == null) { - return def; - } else if (value instanceof String) { - return (String) value; - } else { - throw badKey(key, "string", value); - } - } - - @SuppressWarnings("unchecked") - public static Map optTableField(Map table, String key, Map def) throws LuaException { - var value = table.get(key); - if (value == null) { - return def; - } else if (value instanceof Map) { - return (Map) value; - } else { - throw badKey(key, "table", value); - } - } - - private static double checkReal(String key, double value) throws LuaException { - if (!Double.isFinite(value)) throw badKey(key, "number", getNumericType(value)); - return value; - } -}