1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-04-21 02:03:13 +00:00

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!
This commit is contained in:
Jonathan Coates 2025-03-08 23:39:11 +00:00
parent 1b8344d0a3
commit b97634b717
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
5 changed files with 394 additions and 184 deletions

View File

@ -223,6 +223,9 @@ public interface IArguments {
/**
* Get an argument as a table.
* <p>
* 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.

View File

@ -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.
* <p>
* Much like {@link IArguments}, this allows for convenient parsing of fields from a Lua table.
*
* @param <K> The type of keys in a table, will typically be a wildcard.
* @param <V> The type of values in a table, will typically be a wildcard.
* @see ObjectArguments
*/
public interface LuaTable<K, V> extends Map<K, V> {
/**
@ -29,19 +32,47 @@ public interface LuaTable<K, V> extends Map<K, V> {
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<K, V> extends Map<K, V> {
* 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<K, V> extends Map<K, V> {
* 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<K, V> extends Map<K, V> {
* 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.
* <p>
* 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.
* <p>
* 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<Double> 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<Double> 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<Long> 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<Long> 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<Integer> 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<Integer> 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<Double> 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<Double> 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<Boolean> 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<Boolean> 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<String> 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<String> 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.
* <p>
* 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<Map<?, ?>> 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.
* <p>
* 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<Map<?, ?>> 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) {

View File

@ -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.
*

View File

@ -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<Double> 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<Double> 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());

View File

@ -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<Object, Object> getTableField(Map<?, ?> table, String key) throws LuaException {
var value = table.get(key);
if (value instanceof Map) {
return (Map<Object, Object>) 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<Double> 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<Object, Object> optTableField(Map<?, ?> table, String key, Map<Object, Object> def) throws LuaException {
var value = table.get(key);
if (value == null) {
return def;
} else if (value instanceof Map) {
return (Map<Object, Object>) 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;
}
}