diff --git a/build.gradle b/build.gradle
index 73446fdd6..5e75050db 100644
--- a/build.gradle
+++ b/build.gradle
@@ -109,6 +109,7 @@ accessTransformer file('src/main/resources/META-INF/accesstransformer.cfg')
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.2'
+ testImplementation 'org.hamcrest:hamcrest:2.2'
deployerJars "org.apache.maven.wagon:wagon-ssh:3.0.0"
}
diff --git a/src/main/java/dan200/computercraft/api/lua/ArgumentHelper.java b/src/main/java/dan200/computercraft/api/lua/ArgumentHelper.java
deleted file mode 100644
index 97500fa97..000000000
--- a/src/main/java/dan200/computercraft/api/lua/ArgumentHelper.java
+++ /dev/null
@@ -1,334 +0,0 @@
-/*
- * This file is part of the public ComputerCraft API - http://www.computercraft.info
- * Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
- * For help using the API, and posting your mods, visit the forums at computercraft.info.
- */
-package dan200.computercraft.api.lua;
-
-import dan200.computercraft.api.peripheral.IComputerAccess;
-import dan200.computercraft.api.peripheral.IPeripheral;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import java.util.Map;
-
-/**
- * Provides methods for extracting values and validating Lua arguments, such as those provided to
- * {@link ILuaObject#callMethod(ILuaContext, int, Object[])} or
- * {@link IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}.
- *
- * This provides two sets of functions: the {@code get*} methods, which require an argument to be valid, and
- * {@code opt*}, which accept a default value and return that if the argument was not present or was {@code null}.
- * If the argument is of the wrong type, a suitable error message will be thrown, with a similar format to Lua's own
- * error messages.
- *
- *
Example usage:
- *
- * {@code
- * int slot = getInt( args, 0 );
- * int amount = optInt( args, 1, 64 );
- * }
- *
- */
-public final class ArgumentHelper
-{
- private ArgumentHelper()
- {
- }
-
- /**
- * Get a string representation of the given value's type.
- *
- * @param value The value whose type we are trying to compute.
- * @return A string representation of the given value's type, in a similar format to that provided by Lua's
- * {@code type} function.
- */
- @Nonnull
- public static String getType( @Nullable Object value )
- {
- if( value == null ) return "nil";
- if( value instanceof String ) return "string";
- if( value instanceof Boolean ) return "boolean";
- if( value instanceof Number ) return "number";
- if( value instanceof Map ) return "table";
- return "userdata";
- }
-
- /**
- * Construct a "bad argument" exception, from an expected type and the actual value provided.
- *
- * @param index The argument number, starting from 0.
- * @param expected The expected type for this argument.
- * @param actual The actual value provided for this argument.
- * @return The constructed exception, which should be thrown immediately.
- */
- @Nonnull
- public static LuaException badArgumentOf( int index, @Nonnull String expected, @Nullable Object actual )
- {
- return badArgument( index, expected, getType( actual ) );
- }
-
- /**
- * Construct a "bad argument" exception, from an expected and actual type.
- *
- * @param index The argument number, starting from 0.
- * @param expected The expected type for this argument.
- * @param actual The provided type for this argument.
- * @return The constructed exception, which should be thrown immediately.
- */
- @Nonnull
- public static LuaException badArgument( int index, @Nonnull String expected, @Nonnull String actual )
- {
- return new LuaException( "bad argument #" + (index + 1) + " (" + expected + " expected, got " + actual + ")" );
- }
-
- /**
- * Get an argument as a double.
- *
- * @param args The arguments to extract from.
- * @param index The index into the argument array to read from.
- * @return The argument's value.
- * @throws LuaException If the value is not a number.
- * @see #getFiniteDouble(Object[], int) if you require this to be finite (i.e. not infinite or NaN).
- */
- public static double getDouble( @Nonnull Object[] args, int index ) throws LuaException
- {
- if( index >= args.length ) throw badArgument( index, "number", "nil" );
- Object value = args[index];
- if( !(value instanceof Number) ) throw badArgumentOf( index, "number", value );
- return ((Number) value).doubleValue();
- }
-
- /**
- * Get an argument as an integer.
- *
- * @param args The arguments to extract from.
- * @param index The index into the argument array to read from.
- * @return The argument's value.
- * @throws LuaException If the value is not an integer.
- */
- public static int getInt( @Nonnull Object[] args, int index ) throws LuaException
- {
- return (int) getLong( args, index );
- }
-
- /**
- * Get an argument as a long.
- *
- * @param args The arguments to extract from.
- * @param index The index into the argument array to read from.
- * @return The argument's value.
- * @throws LuaException If the value is not a long.
- */
- public static long getLong( @Nonnull Object[] args, int index ) throws LuaException
- {
- if( index >= args.length ) throw badArgument( index, "number", "nil" );
- Object value = args[index];
- if( !(value instanceof Number) ) throw badArgumentOf( index, "number", value );
- return checkFinite( index, (Number) value ).longValue();
- }
-
- /**
- * Get an argument as a finite number (not infinite or NaN).
- *
- * @param args The arguments to extract from.
- * @param index The index into the argument array to read from.
- * @return The argument's value.
- * @throws LuaException If the value is not finite.
- */
- public static double getFiniteDouble( @Nonnull Object[] args, int index ) throws LuaException
- {
- return checkFinite( index, getDouble( args, index ) );
- }
-
- /**
- * Get an argument as a boolean.
- *
- * @param args The arguments to extract from.
- * @param index The index into the argument array to read from.
- * @return The argument's value.
- * @throws LuaException If the value is not a boolean.
- */
- public static boolean getBoolean( @Nonnull Object[] args, int index ) throws LuaException
- {
- if( index >= args.length ) throw badArgument( index, "boolean", "nil" );
- Object value = args[index];
- if( !(value instanceof Boolean) ) throw badArgumentOf( index, "boolean", value );
- return (Boolean) value;
- }
-
- /**
- * Get an argument as a string.
- *
- * @param args The arguments to extract from.
- * @param index The index into the argument array to read from.
- * @return The argument's value.
- * @throws LuaException If the value is not a string.
- */
- @Nonnull
- public static String getString( @Nonnull Object[] args, int index ) throws LuaException
- {
- if( index >= args.length ) throw badArgument( index, "string", "nil" );
- Object value = args[index];
- if( !(value instanceof String) ) throw badArgumentOf( index, "string", value );
- return (String) value;
- }
-
- /**
- * Get an argument as a table.
- *
- * @param args The arguments to extract from.
- * @param index The index into the argument array to read from.
- * @return The argument's value.
- * @throws LuaException If the value is not a table.
- */
- @Nonnull
- public static Map, ?> getTable( @Nonnull Object[] args, int index ) throws LuaException
- {
- if( index >= args.length ) throw badArgument( index, "table", "nil" );
- Object value = args[index];
- if( !(value instanceof Map) ) throw badArgumentOf( index, "table", value );
- return (Map, ?>) value;
- }
-
- /**
- * Get an argument as a double.
- *
- * @param args The arguments to extract from.
- * @param index The index into the argument array to read from.
- * @param def The default value, if this argument is not given.
- * @return The argument's value, or {@code def} if none was provided.
- * @throws LuaException If the value is not a number.
- */
- public static double optDouble( @Nonnull Object[] args, int index, double def ) throws LuaException
- {
- Object value = index < args.length ? args[index] : null;
- if( value == null ) return def;
- if( !(value instanceof Number) ) throw badArgumentOf( index, "number", value );
- return ((Number) value).doubleValue();
- }
-
- /**
- * Get an argument as an int.
- *
- * @param args The arguments to extract from.
- * @param index The index into the argument array to read from.
- * @param def The default value, if this argument is not given.
- * @return The argument's value, or {@code def} if none was provided.
- * @throws LuaException If the value is not a number.
- */
- public static int optInt( @Nonnull Object[] args, int index, int def ) throws LuaException
- {
- return (int) optLong( args, index, def );
- }
-
- /**
- * Get an argument as a long.
- *
- * @param args The arguments to extract from.
- * @param index The index into the argument array to read from.
- * @param def The default value, if this argument is not given.
- * @return The argument's value, or {@code def} if none was provided.
- * @throws LuaException If the value is not a number.
- */
- public static long optLong( @Nonnull Object[] args, int index, long def ) throws LuaException
- {
- Object value = index < args.length ? args[index] : null;
- if( value == null ) return def;
- if( !(value instanceof Number) ) throw badArgumentOf( index, "number", value );
- return checkFinite( index, (Number) value ).longValue();
- }
-
- /**
- * Get an argument as a finite number (not infinite or NaN).
- *
- * @param args The arguments to extract from.
- * @param index The index into the argument array to read from.
- * @param def The default value, if this argument is not given.
- * @return The argument's value, or {@code def} if none was provided.
- * @throws LuaException If the value is not finite.
- */
- public static double optFiniteDouble( @Nonnull Object[] args, int index, double def ) throws LuaException
- {
- return checkFinite( index, optDouble( args, index, def ) );
- }
-
- /**
- * Get an argument as a boolean.
- *
- * @param args The arguments to extract from.
- * @param index The index into the argument array to read from.
- * @param def The default value, if this argument is not given.
- * @return The argument's value, or {@code def} if none was provided.
- * @throws LuaException If the value is not a boolean.
- */
- public static boolean optBoolean( @Nonnull Object[] args, int index, boolean def ) throws LuaException
- {
- Object value = index < args.length ? args[index] : null;
- if( value == null ) return def;
- if( !(value instanceof Boolean) ) throw badArgumentOf( index, "boolean", value );
- return (Boolean) value;
- }
-
- /**
- * Get an argument as a string.
- *
- * @param args The arguments to extract from.
- * @param index The index into the argument array to read from.
- * @param def The default value, if this argument is not given.
- * @return The argument's value, or {@code def} if none was provided.
- * @throws LuaException If the value is not a string.
- */
- public static String optString( @Nonnull Object[] args, int index, String def ) throws LuaException
- {
- Object value = index < args.length ? args[index] : null;
- if( value == null ) return def;
- if( !(value instanceof String) ) throw badArgumentOf( index, "string", value );
- return (String) value;
- }
-
- /**
- * Get an argument as a table.
- *
- * @param args The arguments to extract from.
- * @param index The index into the argument array to read from.
- * @param def The default value, if this argument is not given.
- * @return The argument's value, or {@code def} if none was provided.
- * @throws LuaException If the value is not a table.
- */
- public static Map, ?> optTable( @Nonnull Object[] args, int index, Map def ) throws LuaException
- {
- Object value = index < args.length ? args[index] : null;
- if( value == null ) return def;
- if( !(value instanceof Map) ) throw badArgumentOf( index, "table", value );
- return (Map, ?>) value;
- }
-
- private static Number checkFinite( int index, Number value ) throws LuaException
- {
- checkFinite( index, value.doubleValue() );
- return value;
- }
-
- private static double checkFinite( int index, double value ) throws LuaException
- {
- if( !Double.isFinite( value ) ) throw badArgument( index, "number", getNumericType( value ) );
- return value;
- }
-
- /**
- * Returns a more detailed representation of this number's type. If this is finite, it will just return "number",
- * otherwise it returns whether it is infinite or NaN.
- *
- * @param value The value to extract the type for.
- * @return This value's numeric type.
- */
- @Nonnull
- public static String getNumericType( double value )
- {
- if( Double.isNaN( value ) ) return "nan";
- if( value == Double.POSITIVE_INFINITY ) return "inf";
- if( value == Double.NEGATIVE_INFINITY ) return "-inf";
- return "number";
- }
-}
diff --git a/src/main/java/dan200/computercraft/api/lua/IArguments.java b/src/main/java/dan200/computercraft/api/lua/IArguments.java
new file mode 100644
index 000000000..c19ed4526
--- /dev/null
+++ b/src/main/java/dan200/computercraft/api/lua/IArguments.java
@@ -0,0 +1,407 @@
+/*
+ * This file is part of the public ComputerCraft API - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
+ * For help using the API, and posting your mods, visit the forums at computercraft.info.
+ */
+package dan200.computercraft.api.lua;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.Optional;
+
+import static dan200.computercraft.api.lua.LuaValues.checkFinite;
+
+/**
+ * The arguments passed to a function.
+ */
+public interface IArguments
+{
+ /**
+ * Get the number of arguments passed to this function.
+ *
+ * @return The number of passed arguments.
+ */
+ int count();
+
+ /**
+ * Get the argument at the specific index. The returned value must obey the following conversion rules:
+ *
+ *
+ * Lua values of type "string" will be represented by a {@link String}.
+ * Lua values of type "number" will be represented by a {@link Number}.
+ * Lua values of type "boolean" will be represented by a {@link Boolean}.
+ * Lua values of type "table" will be represented by a {@link Map}.
+ * Lua values of any other type will be represented by a {@code null} value.
+ *
+ *
+ * @param index The argument number.
+ * @return The argument's value, or {@code null} if not present.
+ */
+ @Nullable
+ Object get( int index );
+
+ /**
+ * Drop a number of arguments. The returned arguments instance will access arguments at position {@code i + count},
+ * rather than {@code i}. However, errors will still use the given argument index.
+ *
+ * @param count The number of arguments to drop.
+ * @return The new {@link IArguments} instance.
+ */
+ IArguments drop( int count );
+
+ default Object[] getAll()
+ {
+ Object[] result = new Object[count()];
+ for( int i = 0; i < result.length; i++ ) result[i] = get( i );
+ return result;
+ }
+
+ /**
+ * Get an argument as a double.
+ *
+ * @param index The argument number.
+ * @return The argument'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).
+ */
+ default double getDouble( int index ) throws LuaException
+ {
+ Object value = get( index );
+ if( !(value instanceof Number) ) throw LuaValues.badArgumentOf( index, "number", value );
+ return ((Number) value).doubleValue();
+ }
+
+ /**
+ * Get an argument as an integer.
+ *
+ * @param index The argument number.
+ * @return The argument's value.
+ * @throws LuaException If the value is not an integer.
+ */
+ default int getInt( int index ) throws LuaException
+ {
+ return (int) getLong( index );
+ }
+
+ /**
+ * Get an argument as a long.
+ *
+ * @param index The argument number.
+ * @return The argument's value.
+ * @throws LuaException If the value is not a long.
+ */
+ default long getLong( int index ) throws LuaException
+ {
+ Object value = get( index );
+ if( !(value instanceof Number) ) throw LuaValues.badArgumentOf( index, "number", value );
+ return LuaValues.checkFiniteNum( index, (Number) value ).longValue();
+ }
+
+ /**
+ * Get an argument as a finite number (not infinite or NaN).
+ *
+ * @param index The argument number.
+ * @return The argument's value.
+ * @throws LuaException If the value is not finite.
+ */
+ default double getFiniteDouble( int index ) throws LuaException
+ {
+ return checkFinite( index, getDouble( index ) );
+ }
+
+ /**
+ * Get an argument as a boolean.
+ *
+ * @param index The argument number.
+ * @return The argument's value.
+ * @throws LuaException If the value is not a boolean.
+ */
+ default boolean getBoolean( int index ) throws LuaException
+ {
+ Object value = get( index );
+ if( !(value instanceof Boolean) ) throw LuaValues.badArgumentOf( index, "boolean", value );
+ return (Boolean) value;
+ }
+
+ /**
+ * Get an argument as a string.
+ *
+ * @param index The argument number.
+ * @return The argument's value.
+ * @throws LuaException If the value is not a string.
+ */
+ @Nonnull
+ default String getString( int index ) throws LuaException
+ {
+ Object value = get( index );
+ if( !(value instanceof String) ) throw LuaValues.badArgumentOf( index, "string", value );
+ return (String) value;
+ }
+
+ /**
+ * Get a string argument as a byte array.
+ *
+ * @param index The argument number.
+ * @return The argument's value. This is a read only buffer.
+ * @throws LuaException If the value is not a string.
+ */
+ @Nonnull
+ default ByteBuffer getBytes( int index ) throws LuaException
+ {
+ return LuaValues.encode( getString( index ) );
+ }
+
+ /**
+ * Get a string argument as an enum value.
+ *
+ * @param index The argument number.
+ * @param klass The type of enum to parse.
+ * @param The type of enum to parse.
+ * @return The argument's value.
+ * @throws LuaException If the value is not a string or not a valid option for this enum.
+ */
+ @Nonnull
+ default > T getEnum( int index, Class klass ) throws LuaException
+ {
+ return LuaValues.checkEnum( index, klass, getString( index ) );
+ }
+
+ /**
+ * Get an argument as a table.
+ *
+ * @param index The argument number.
+ * @return The argument's value.
+ * @throws LuaException If the value is not a table.
+ */
+ @Nonnull
+ default Map, ?> getTable( int index ) throws LuaException
+ {
+ Object value = get( index );
+ if( !(value instanceof Map) ) throw LuaValues.badArgumentOf( index, "table", value );
+ return (Map, ?>) value;
+ }
+
+ /**
+ * Get an argument as a double.
+ *
+ * @param index The argument number.
+ * @return The argument's value, or {@link Optional#empty()} if not present.
+ * @throws LuaException If the value is not a number.
+ */
+ @Nonnull
+ default Optional optDouble( int index ) throws LuaException
+ {
+ Object value = get( index );
+ if( value == null ) return Optional.empty();
+ if( !(value instanceof Number) ) throw LuaValues.badArgumentOf( index, "number", value );
+ return Optional.of( ((Number) value).doubleValue() );
+ }
+
+ /**
+ * Get an argument as an int.
+ *
+ * @param index The argument number.
+ * @return The argument's value, or {@link Optional#empty()} if not present.
+ * @throws LuaException If the value is not a number.
+ */
+ @Nonnull
+ default Optional optInt( int index ) throws LuaException
+ {
+ return optLong( index ).map( Long::intValue );
+ }
+
+ /**
+ * Get an argument as a long.
+ *
+ * @param index The argument number.
+ * @return The argument's value, or {@link Optional#empty()} if not present.
+ * @throws LuaException If the value is not a number.
+ */
+ default Optional optLong( int index ) throws LuaException
+ {
+ Object value = get( index );
+ if( value == null ) return Optional.empty();
+ if( !(value instanceof Number) ) throw LuaValues.badArgumentOf( index, "number", value );
+ return Optional.of( LuaValues.checkFiniteNum( index, (Number) value ).longValue() );
+ }
+
+ /**
+ * Get an argument as a finite number (not infinite or NaN).
+ *
+ * @param index The argument number.
+ * @return The argument's value, or {@link Optional#empty()} if not present.
+ * @throws LuaException If the value is not finite.
+ */
+ default Optional optFiniteDouble( int index ) throws LuaException
+ {
+ Optional value = optDouble( index );
+ if( value.isPresent() ) LuaValues.checkFiniteNum( index, value.get() );
+ return value;
+ }
+
+ /**
+ * Get an argument as a boolean.
+ *
+ * @param index The argument number.
+ * @return The argument's value, or {@link Optional#empty()} if not present.
+ * @throws LuaException If the value is not a boolean.
+ */
+ default Optional optBoolean( int index ) throws LuaException
+ {
+ Object value = get( index );
+ if( value == null ) return Optional.empty();
+ if( !(value instanceof Boolean) ) throw LuaValues.badArgumentOf( index, "boolean", value );
+ return Optional.of( (Boolean) value );
+ }
+
+ /**
+ * Get an argument as a string.
+ *
+ * @param index The argument number.
+ * @return The argument's value, or {@link Optional#empty()} if not present.
+ * @throws LuaException If the value is not a string.
+ */
+ default Optional optString( int index ) throws LuaException
+ {
+ Object value = get( index );
+ if( value == null ) return Optional.empty();
+ if( !(value instanceof String) ) throw LuaValues.badArgumentOf( index, "string", value );
+ return Optional.of( (String) value );
+ }
+
+ /**
+ * Get a string argument as a byte array.
+ *
+ * @param index The argument number.
+ * @return The argument's value, or {@link Optional#empty()} if not present. This is a read only buffer.
+ * @throws LuaException If the value is not a string.
+ */
+ default Optional optBytes( int index ) throws LuaException
+ {
+ return optString( index ).map( LuaValues::encode );
+ }
+
+ /**
+ * Get a string argument as an enum value.
+ *
+ * @param index The argument number.
+ * @param klass The type of enum to parse.
+ * @param The type of enum to parse.
+ * @return The argument's value.
+ * @throws LuaException If the value is not a string or not a valid option for this enum.
+ */
+ @Nonnull
+ default > Optional optEnum( int index, Class klass ) throws LuaException
+ {
+ Optional str = optString( index );
+ return str.isPresent() ? Optional.of( LuaValues.checkEnum( index, klass, str.get() ) ) : Optional.empty();
+ }
+
+ /**
+ * Get an argument as a table.
+ *
+ * @param index The argument number.
+ * @return The argument's value, or {@link Optional#empty()} if not present.
+ * @throws LuaException If the value is not a table.
+ */
+ default Optional> optTable( int index ) throws LuaException
+ {
+ Object value = get( index );
+ if( value == null ) return Optional.empty();
+ if( !(value instanceof Map) ) throw LuaValues.badArgumentOf( index, "map", value );
+ return Optional.of( (Map, ?>) value );
+ }
+
+ /**
+ * Get an argument as a double.
+ *
+ * @param index The argument number.
+ * @param def The default value, if this argument is not given.
+ * @return The argument's value, or {@code def} if none was provided.
+ * @throws LuaException If the value is not a number.
+ */
+ default double optDouble( int index, double def ) throws LuaException
+ {
+ return optDouble( index ).orElse( def );
+ }
+
+ /**
+ * Get an argument as an int.
+ *
+ * @param index The argument number.
+ * @param def The default value, if this argument is not given.
+ * @return The argument's value, or {@code def} if none was provided.
+ * @throws LuaException If the value is not a number.
+ */
+ default int optInt( int index, int def ) throws LuaException
+ {
+ return optInt( index ).orElse( def );
+ }
+
+ /**
+ * Get an argument as a long.
+ *
+ * @param index The argument number.
+ * @param def The default value, if this argument is not given.
+ * @return The argument's value, or {@code def} if none was provided.
+ * @throws LuaException If the value is not a number.
+ */
+ default long optLong( int index, long def ) throws LuaException
+ {
+ return optLong( index ).orElse( def );
+ }
+
+ /**
+ * Get an argument as a finite number (not infinite or NaN).
+ *
+ * @param index The argument number.
+ * @param def The default value, if this argument is not given.
+ * @return The argument's value, or {@code def} if none was provided.
+ * @throws LuaException If the value is not finite.
+ */
+ default double optFiniteDouble( int index, double def ) throws LuaException
+ {
+ return optFiniteDouble( index ).orElse( def );
+ }
+
+ /**
+ * Get an argument as a boolean.
+ *
+ * @param index The argument number.
+ * @param def The default value, if this argument is not given.
+ * @return The argument's value, or {@code def} if none was provided.
+ * @throws LuaException If the value is not a boolean.
+ */
+ default boolean optBoolean( int index, boolean def ) throws LuaException
+ {
+ return optBoolean( index ).orElse( def );
+ }
+
+ /**
+ * Get an argument as a string.
+ *
+ * @param index The argument number.
+ * @param def The default value, if this argument is not given.
+ * @return The argument's value, or {@code def} if none was provided.
+ * @throws LuaException If the value is not a string.
+ */
+ default String optString( int index, String def ) throws LuaException
+ {
+ return optString( index ).orElse( def );
+ }
+
+ /**
+ * Get an argument as a table.
+ *
+ * @param index The argument number.
+ * @param def The default value, if this argument is not given.
+ * @return The argument's value, or {@code def} if none was provided.
+ * @throws LuaException If the value is not a table.
+ */
+ default Map, ?> optTable( int index, Map def ) throws LuaException
+ {
+ return optTable( index ).orElse( def );
+ }
+}
diff --git a/src/main/java/dan200/computercraft/api/lua/IDynamicLuaObject.java b/src/main/java/dan200/computercraft/api/lua/IDynamicLuaObject.java
new file mode 100644
index 000000000..13b2c7a8f
--- /dev/null
+++ b/src/main/java/dan200/computercraft/api/lua/IDynamicLuaObject.java
@@ -0,0 +1,45 @@
+/*
+ * This file is part of the public ComputerCraft API - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
+ * For help using the API, and posting your mods, visit the forums at computercraft.info.
+ */
+package dan200.computercraft.api.lua;
+
+import dan200.computercraft.api.peripheral.IDynamicPeripheral;
+
+import javax.annotation.Nonnull;
+
+/**
+ * An interface for representing custom objects returned by peripherals or other Lua objects.
+ *
+ * Generally, one does not need to implement this type - it is sufficient to return an object with some methods
+ * annotated with {@link LuaFunction}. {@link IDynamicLuaObject} is useful when you wish your available methods to
+ * change at runtime.
+ */
+public interface IDynamicLuaObject
+{
+ /**
+ * Get the names of the methods that this object implements. This should not change over the course of the object's
+ * lifetime.
+ *
+ * @return The method names this object provides.
+ * @see IDynamicPeripheral#getMethodNames()
+ */
+ @Nonnull
+ String[] getMethodNames();
+
+ /**
+ * Called when a user calls one of the methods that this object implements.
+ *
+ * @param context The context of the currently running lua thread. This can be used to wait for events
+ * or otherwise yield.
+ * @param method An integer identifying which method index from {@link #getMethodNames()} the computer wishes
+ * to call.
+ * @param arguments The arguments for this method.
+ * @return The result of this function. Either an immediate value ({@link MethodResult#of(Object...)} or an
+ * instruction to yield.
+ * @throws LuaException If the function threw an exception.
+ */
+ @Nonnull
+ MethodResult callMethod( @Nonnull ILuaContext context, int method, @Nonnull IArguments arguments ) throws LuaException;
+}
diff --git a/src/main/java/dan200/computercraft/api/lua/ILuaAPI.java b/src/main/java/dan200/computercraft/api/lua/ILuaAPI.java
index a37f0404a..ae405e3aa 100644
--- a/src/main/java/dan200/computercraft/api/lua/ILuaAPI.java
+++ b/src/main/java/dan200/computercraft/api/lua/ILuaAPI.java
@@ -8,7 +8,8 @@
import dan200.computercraft.api.ComputerCraftAPI;
/**
- * Represents a {@link ILuaObject} which is stored as a global variable on computer startup.
+ * Represents a Lua object which is stored as a global variable on computer startup. This must either provide
+ * {@link LuaFunction} annotated functions or implement {@link IDynamicLuaObject}.
*
* Before implementing this interface, consider alternative methods of providing methods. It is generally preferred
* to use peripherals to provide functionality to users.
@@ -16,7 +17,7 @@
* @see ILuaAPIFactory
* @see ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory)
*/
-public interface ILuaAPI extends ILuaObject
+public interface ILuaAPI
{
/**
* Get the globals this API will be assigned to. This will override any other global, so you should
diff --git a/src/main/java/dan200/computercraft/api/lua/ILuaCallback.java b/src/main/java/dan200/computercraft/api/lua/ILuaCallback.java
new file mode 100644
index 000000000..f05d82a73
--- /dev/null
+++ b/src/main/java/dan200/computercraft/api/lua/ILuaCallback.java
@@ -0,0 +1,27 @@
+/*
+ * This file is part of the public ComputerCraft API - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
+ * For help using the API, and posting your mods, visit the forums at computercraft.info.
+ */
+package dan200.computercraft.api.lua;
+
+import javax.annotation.Nonnull;
+
+/**
+ * A continuation which is called when this coroutine is resumed.
+ *
+ * @see MethodResult#yield(Object[], ILuaCallback)
+ */
+public interface ILuaCallback
+{
+ /**
+ * Resume this coroutine.
+ *
+ * @param args The result of resuming this coroutine. These will have the same form as described in
+ * {@link LuaFunction}.
+ * @return The result of this continuation. Either the result to return to the callee, or another yield.
+ * @throws LuaException On an error.
+ */
+ @Nonnull
+ MethodResult resume( Object[] args ) throws LuaException;
+}
diff --git a/src/main/java/dan200/computercraft/api/lua/ILuaContext.java b/src/main/java/dan200/computercraft/api/lua/ILuaContext.java
index 623f19a74..569b61e5b 100644
--- a/src/main/java/dan200/computercraft/api/lua/ILuaContext.java
+++ b/src/main/java/dan200/computercraft/api/lua/ILuaContext.java
@@ -6,99 +6,25 @@
package dan200.computercraft.api.lua;
import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
/**
- * An interface passed to peripherals and {@link ILuaObject}s by computers or turtles, providing methods
- * that allow the peripheral call to wait for events before returning, just like in lua. This is very useful if you need
- * to signal work to be performed on the main thread, and don't want to return until the work has been completed.
+ * An interface passed to peripherals and {@link IDynamicLuaObject}s by computers or turtles, providing methods
+ * that allow the peripheral call to interface with the computer.
*/
public interface ILuaContext
{
- /**
- * Wait for an event to occur on the computer, suspending the thread until it arises. This method is exactly
- * equivalent to {@code os.pullEvent()} in lua.
- *
- * @param filter A specific event to wait for, or null to wait for any event.
- * @return An object array containing the name of the event that occurred, and any event parameters.
- * @throws LuaException If the user presses CTRL+T to terminate the current program while pullEvent() is
- * waiting for an event, a "Terminated" exception will be thrown here.
- *
- * Do not attempt to catch this exception. You should use {@link #pullEventRaw(String)}
- * should you wish to disable termination.
- * @throws InterruptedException If the user shuts down or reboots the computer while pullEvent() is waiting for an
- * event, InterruptedException will be thrown. This exception must not be caught or
- * intercepted, or the computer will leak memory and end up in a broken state.
- */
- @Nonnull
- default Object[] pullEvent( @Nullable String filter ) throws LuaException, InterruptedException
- {
- Object[] results = pullEventRaw( filter );
- if( results.length >= 1 && results[0].equals( "terminate" ) ) throw new LuaException( "Terminated", 0 );
- return results;
- }
-
- /**
- * The same as {@link #pullEvent(String)}, except "terminated" events are ignored. Only use this if you want to
- * prevent program termination, which is not recommended. This method is exactly equivalent to
- * {@code os.pullEventRaw()} in lua.
- *
- * @param filter A specific event to wait for, or null to wait for any event.
- * @return An object array containing the name of the event that occurred, and any event parameters.
- * @throws InterruptedException If the user shuts down or reboots the computer while pullEventRaw() is waiting for
- * an event, InterruptedException will be thrown. This exception must not be caught or
- * intercepted, or the computer will leak memory and end up in a broken state.
- * @see #pullEvent(String)
- */
- @Nonnull
- default Object[] pullEventRaw( @Nullable String filter ) throws InterruptedException
- {
- return yield( new Object[] { filter } );
- }
-
- /**
- * Yield the current coroutine with some arguments until it is resumed. This method is exactly equivalent to
- * {@code coroutine.yield()} in lua. Use {@code pullEvent()} if you wish to wait for events.
- *
- * @param arguments An object array containing the arguments to pass to coroutine.yield()
- * @return An object array containing the return values from coroutine.yield()
- * @throws InterruptedException If the user shuts down or reboots the computer the coroutine is suspended,
- * InterruptedException will be thrown. This exception must not be caught or
- * intercepted, or the computer will leak memory and end up in a broken state.
- * @see #pullEvent(String)
- */
- @Nonnull
- Object[] yield( @Nullable Object[] arguments ) throws InterruptedException;
-
- /**
- * Queue a task to be executed on the main server thread at the beginning of next tick, waiting for it to complete.
- * This should be used when you need to interact with the world in a thread-safe manner.
- *
- * Note that the return values of your task are handled as events, meaning more complex objects such as maps or
- * {@link ILuaObject} will not preserve their identities.
- *
- * @param task The task to execute on the main thread.
- * @return The objects returned by {@code task}.
- * @throws LuaException If the task could not be queued, or if the task threw an exception.
- * @throws InterruptedException If the user shuts down or reboots the computer the coroutine is suspended,
- * InterruptedException will be thrown. This exception must not be caught or
- * intercepted, or the computer will leak memory and end up in a broken state.
- */
- @Nullable
- Object[] executeMainThreadTask( @Nonnull ILuaTask task ) throws LuaException, InterruptedException;
-
/**
* Queue a task to be executed on the main server thread at the beginning of next tick, but do not wait for it to
* complete. This should be used when you need to interact with the world in a thread-safe manner but do not care
* about the result or you wish to run asynchronously.
*
* When the task has finished, it will enqueue a {@code task_completed} event, which takes the task id, a success
- * value and the return values, or an error message if it failed. If you need to wait on this event, it may be
- * better to use {@link #executeMainThreadTask(ILuaTask)}.
+ * value and the return values, or an error message if it failed.
*
* @param task The task to execute on the main thread.
* @return The "id" of the task. This will be the first argument to the {@code task_completed} event.
* @throws LuaException If the task could not be queued.
+ * @see LuaFunction#mainThread() To run functions on the main thread and return their results synchronously.
*/
long issueMainThreadTask( @Nonnull ILuaTask task ) throws LuaException;
}
diff --git a/src/main/java/dan200/computercraft/api/lua/ILuaObject.java b/src/main/java/dan200/computercraft/api/lua/ILuaObject.java
deleted file mode 100644
index 4d0f37f89..000000000
--- a/src/main/java/dan200/computercraft/api/lua/ILuaObject.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * This file is part of the public ComputerCraft API - http://www.computercraft.info
- * Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
- * For help using the API, and posting your mods, visit the forums at computercraft.info.
- */
-package dan200.computercraft.api.lua;
-
-import dan200.computercraft.api.peripheral.IComputerAccess;
-import dan200.computercraft.api.peripheral.IPeripheral;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-/**
- * An interface for representing custom objects returned by {@link IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}
- * calls.
- *
- * Return objects implementing this interface to expose objects with methods to lua.
- */
-public interface ILuaObject
-{
- /**
- * Get the names of the methods that this object implements. This works the same as {@link IPeripheral#getMethodNames()}.
- * See that method for detailed documentation.
- *
- * @return The method names this object provides.
- * @see IPeripheral#getMethodNames()
- */
- @Nonnull
- String[] getMethodNames();
-
- /**
- * Called when a user calls one of the methods that this object implements. This works the same as
- * {@link IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}}. See that method for detailed
- * documentation.
- *
- * @param context The context of the currently running lua thread. This can be used to wait for events
- * or otherwise yield.
- * @param method An integer identifying which of the methods from getMethodNames() the computercraft
- * wishes to call. The integer indicates the index into the getMethodNames() table
- * that corresponds to the string passed into peripheral.call()
- * @param arguments The arguments for this method. See {@link IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}
- * the possible values and conversion rules.
- * @return An array of objects, representing the values you wish to return to the Lua program.
- * See {@link IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])} for the valid values and
- * conversion rules.
- * @throws LuaException If the task could not be queued, or if the task threw an exception.
- * @throws InterruptedException If the user shuts down or reboots the computer the coroutine is suspended,
- * InterruptedException will be thrown. This exception must not be caught or
- * intercepted, or the computer will leak memory and end up in a broken state.w
- * @see IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])
- */
- @Nullable
- Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException;
-}
diff --git a/src/main/java/dan200/computercraft/api/lua/ILuaTask.java b/src/main/java/dan200/computercraft/api/lua/ILuaTask.java
index f99ce283b..4ea060358 100644
--- a/src/main/java/dan200/computercraft/api/lua/ILuaTask.java
+++ b/src/main/java/dan200/computercraft/api/lua/ILuaTask.java
@@ -8,11 +8,10 @@
import javax.annotation.Nullable;
/**
- * A task which can be executed via {@link ILuaContext#executeMainThreadTask(ILuaTask)} or
- * {@link ILuaContext#issueMainThreadTask(ILuaTask)}. This will be run on the main thread, at the beginning of the
+ * A task which can be executed via {@link ILuaContext#issueMainThreadTask(ILuaTask)} This will be run on the main
+ * thread, at the beginning of the
* next tick.
*
- * @see ILuaContext#executeMainThreadTask(ILuaTask)
* @see ILuaContext#issueMainThreadTask(ILuaTask)
*/
@FunctionalInterface
@@ -21,8 +20,7 @@ public interface ILuaTask
/**
* Execute this task.
*
- * @return The arguments to add to the {@code task_completed} event. These will be returned by
- * {@link ILuaContext#executeMainThreadTask(ILuaTask)}.
+ * @return The arguments to add to the {@code task_completed} event.
* @throws LuaException If you throw any exception from this function, a lua error will be raised with the
* same message as your exception. Use this to throw appropriate errors if the wrong
* arguments are supplied to your method.
diff --git a/src/main/java/dan200/computercraft/api/lua/LuaException.java b/src/main/java/dan200/computercraft/api/lua/LuaException.java
index 65d8d0284..949a8eeba 100644
--- a/src/main/java/dan200/computercraft/api/lua/LuaException.java
+++ b/src/main/java/dan200/computercraft/api/lua/LuaException.java
@@ -13,24 +13,33 @@
public class LuaException extends Exception
{
private static final long serialVersionUID = -6136063076818512651L;
+ private final boolean hasLevel;
private final int level;
- public LuaException()
- {
- this( "error", 1 );
- }
-
public LuaException( @Nullable String message )
{
- this( message, 1 );
+ super( message );
+ this.hasLevel = false;
+ this.level = 1;
}
public LuaException( @Nullable String message, int level )
{
super( message );
+ this.hasLevel = true;
this.level = level;
}
+ /**
+ * Whether a level was explicitly specified when constructing. This is used to determine
+ *
+ * @return Whether this has an explicit level.
+ */
+ public boolean hasLevel()
+ {
+ return hasLevel;
+ }
+
/**
* The level this error is raised at. Level 1 is the function's caller, level 2 is that function's caller, and so
* on.
diff --git a/src/main/java/dan200/computercraft/api/lua/LuaFunction.java b/src/main/java/dan200/computercraft/api/lua/LuaFunction.java
new file mode 100644
index 000000000..e17e30ac4
--- /dev/null
+++ b/src/main/java/dan200/computercraft/api/lua/LuaFunction.java
@@ -0,0 +1,58 @@
+/*
+ * This file is part of the public ComputerCraft API - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
+ * For help using the API, and posting your mods, visit the forums at computercraft.info.
+ */
+package dan200.computercraft.api.lua;
+
+import dan200.computercraft.api.peripheral.IComputerAccess;
+import dan200.computercraft.api.peripheral.IPeripheral;
+
+import java.lang.annotation.*;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * Used to mark a Java function which is callable from Lua.
+ *
+ * Methods annotated with {@link LuaFunction} must be public final instance methods. They can have any number of
+ * parameters, but they must be of the following types:
+ *
+ *
+ * {@link ILuaContext} (and {@link IComputerAccess} if on a {@link IPeripheral})
+ * {@link IArguments}: The arguments supplied to this function.
+ *
+ * Alternatively, one may specify the desired arguments as normal parameters and the argument parsing code will
+ * be generated automatically.
+ *
+ * Each parameter must be one of the given types supported by {@link IArguments} (for instance, {@link int} or
+ * {@link Map}). Optional values are supported by accepting a parameter of type {@link Optional}.
+ *
+ *
+ *
+ * This function may return {@link MethodResult}. However, if you simply return a value (rather than having to yield),
+ * you may return {@code void}, a single value (either an object or a primitive like {@code int}) or array of objects.
+ * These will be treated the same as {@link MethodResult#of()}, {@link MethodResult#of(Object)} and
+ * {@link MethodResult#of(Object...)}.
+ */
+@Documented
+@Retention( RetentionPolicy.RUNTIME )
+@Target( ElementType.METHOD )
+public @interface LuaFunction
+{
+ /**
+ * Explicitly specify the method names of this function. If not given, it uses the name of the annotated method.
+ *
+ * @return This function's name(s).
+ */
+ String[] value() default {};
+
+ /**
+ * Run this function on the main server thread. This should be specified for any method which interacts with
+ * Minecraft in a thread-unsafe manner.
+ *
+ * @return Whether this functi
+ * @see ILuaContext#issueMainThreadTask(ILuaTask)
+ */
+ boolean mainThread() default false;
+}
diff --git a/src/main/java/dan200/computercraft/api/lua/LuaValues.java b/src/main/java/dan200/computercraft/api/lua/LuaValues.java
new file mode 100644
index 000000000..f89b24a17
--- /dev/null
+++ b/src/main/java/dan200/computercraft/api/lua/LuaValues.java
@@ -0,0 +1,152 @@
+/*
+ * This file is part of the public ComputerCraft API - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
+ * For help using the API, and posting your mods, visit the forums at computercraft.info.
+ */
+package dan200.computercraft.api.lua;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.nio.ByteBuffer;
+import java.util.Map;
+
+/**
+ * Various utility functions for operating with Lua values.
+ *
+ * @see IArguments
+ */
+public final class LuaValues
+{
+ private LuaValues()
+ {
+ }
+
+ /**
+ * Encode a Lua string into a read-only {@link ByteBuffer}.
+ *
+ * @param string The string to encode.
+ * @return The encoded string.
+ */
+ @Nonnull
+ public static ByteBuffer encode( @Nonnull String string )
+ {
+ byte[] chars = new byte[string.length()];
+ for( int i = 0; i < chars.length; i++ )
+ {
+ char c = string.charAt( i );
+ chars[i] = c < 256 ? (byte) c : 63;
+ }
+
+ return ByteBuffer.wrap( chars ).asReadOnlyBuffer();
+ }
+
+ /**
+ * Returns a more detailed representation of this number's type. If this is finite, it will just return "number",
+ * otherwise it returns whether it is infinite or NaN.
+ *
+ * @param value The value to extract the type for.
+ * @return This value's numeric type.
+ */
+ @Nonnull
+ public static String getNumericType( double value )
+ {
+ if( Double.isNaN( value ) ) return "nan";
+ if( value == Double.POSITIVE_INFINITY ) return "inf";
+ if( value == Double.NEGATIVE_INFINITY ) return "-inf";
+ return "number";
+ }
+
+ /**
+ * Get a string representation of the given value's type.
+ *
+ * @param value The value whose type we are trying to compute.
+ * @return A string representation of the given value's type, in a similar format to that provided by Lua's
+ * {@code type} function.
+ */
+ @Nonnull
+ public static String getType( @Nullable Object value )
+ {
+ if( value == null ) return "nil";
+ if( value instanceof String ) return "string";
+ if( value instanceof Boolean ) return "boolean";
+ if( value instanceof Number ) return "number";
+ if( value instanceof Map ) return "table";
+ return "userdata";
+ }
+
+ /**
+ * Construct a "bad argument" exception, from an expected type and the actual value provided.
+ *
+ * @param index The argument number, starting from 0.
+ * @param expected The expected type for this argument.
+ * @param actual The actual value provided for this argument.
+ * @return The constructed exception, which should be thrown immediately.
+ */
+ @Nonnull
+ public static LuaException badArgumentOf( int index, @Nonnull String expected, @Nullable Object actual )
+ {
+ return badArgument( index, expected, getType( actual ) );
+ }
+
+ /**
+ * Construct a "bad argument" exception, from an expected and actual type.
+ *
+ * @param index The argument number, starting from 0.
+ * @param expected The expected type for this argument.
+ * @param actual The provided type for this argument.
+ * @return The constructed exception, which should be thrown immediately.
+ */
+ @Nonnull
+ public static LuaException badArgument( int index, @Nonnull String expected, @Nonnull String actual )
+ {
+ return new LuaException( "bad argument #" + (index + 1) + " (" + expected + " expected, got " + actual + ")" );
+ }
+
+ /**
+ * Ensure a numeric argument is finite (i.e. not infinite or {@link Double#NaN}.
+ *
+ * @param index The argument index to check.
+ * @param value The value to check.
+ * @return The input {@code value}.
+ * @throws LuaException If this is not a finite number.
+ */
+ public static Number checkFiniteNum( int index, Number value ) throws LuaException
+ {
+ checkFinite( index, value.doubleValue() );
+ return value;
+ }
+
+ /**
+ * Ensure a numeric argument is finite (i.e. not infinite or {@link Double#NaN}.
+ *
+ * @param index The argument index to check.
+ * @param value The value to check.
+ * @return The input {@code value}.
+ * @throws LuaException If this is not a finite number.
+ */
+ public static double checkFinite( int index, double value ) throws LuaException
+ {
+ if( !Double.isFinite( value ) ) throw badArgument( index, "number", getNumericType( value ) );
+ return value;
+ }
+
+ /**
+ * Ensure a string is a valid enum value.
+ *
+ * @param index The argument index to check.
+ * @param klass The class of the enum instance.
+ * @param value The value to extract.
+ * @param The type of enum we are extracting.
+ * @return The parsed enum value.
+ * @throws LuaException If this is not a known enum value.
+ */
+ public static > T checkEnum( int index, Class klass, String value ) throws LuaException
+ {
+ for( T possibility : klass.getEnumConstants() )
+ {
+ if( possibility.name().equalsIgnoreCase( value ) ) return possibility;
+ }
+
+ throw new LuaException( "bad argument #" + (index + 1) + " (unknown option " + value + ")" );
+ }
+}
diff --git a/src/main/java/dan200/computercraft/api/lua/MethodResult.java b/src/main/java/dan200/computercraft/api/lua/MethodResult.java
new file mode 100644
index 000000000..2a7f80155
--- /dev/null
+++ b/src/main/java/dan200/computercraft/api/lua/MethodResult.java
@@ -0,0 +1,170 @@
+/*
+ * This file is part of the public ComputerCraft API - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
+ * For help using the API, and posting your mods, visit the forums at computercraft.info.
+ */
+package dan200.computercraft.api.lua;
+
+import dan200.computercraft.api.peripheral.IComputerAccess;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * The result of invoking a Lua method.
+ *
+ * Method results either return a value immediately ({@link #of(Object...)} or yield control to the parent coroutine.
+ * When the current coroutine is resumed, we invoke the provided {@link ILuaCallback#resume(Object[])} callback.
+ */
+public final class MethodResult
+{
+ private static final MethodResult empty = new MethodResult( null, null );
+
+ private final Object[] result;
+ private final ILuaCallback callback;
+ private final int adjust;
+
+ private MethodResult( Object[] arguments, ILuaCallback callback )
+ {
+ this.result = arguments;
+ this.callback = callback;
+ this.adjust = 0;
+ }
+
+ private MethodResult( Object[] arguments, ILuaCallback callback, int adjust )
+ {
+ this.result = arguments;
+ this.callback = callback;
+ this.adjust = adjust;
+ }
+
+ /**
+ * Return no values immediately.
+ *
+ * @return A method result which returns immediately with no values.
+ */
+ @Nonnull
+ public static MethodResult of()
+ {
+ return empty;
+ }
+
+ /**
+ * Return a single value immediately.
+ *
+ * Integers, doubles, floats, strings, booleans, {@link Map}, {@link Collection}s, arrays and {@code null} will be
+ * converted to their corresponding Lua type. {@code byte[]} and {@link ByteBuffer} will be treated as binary
+ * strings.
+ *
+ * In order to provide a custom object with methods, one may return a {@link IDynamicLuaObject}, or an arbitrary
+ * class with {@link LuaFunction} annotations. Anything else will be converted to {@code nil}.
+ *
+ * @param value The value to return to the calling Lua function.
+ * @return A method result which returns immediately with the given value.
+ */
+ @Nonnull
+ public static MethodResult of( @Nullable Object value )
+ {
+ return new MethodResult( new Object[] { value }, null );
+ }
+
+ /**
+ * Return any number of values immediately.
+ *
+ * @param values The values to return. See {@link #of(Object)} for acceptable values.
+ * @return A method result which returns immediately with the given values.
+ */
+ @Nonnull
+ public static MethodResult of( @Nullable Object... values )
+ {
+ return values == null || values.length == 0 ? empty : new MethodResult( values, null );
+ }
+
+ /**
+ * Wait for an event to occur on the computer, suspending the thread until it arises. This method is exactly
+ * equivalent to {@code os.pullEvent()} in lua.
+ *
+ * @param filter A specific event to wait for, or null to wait for any event.
+ * @param callback The callback to resume with the name of the event that occurred, and any event parameters.
+ * @return The method result which represents this yield.
+ * @see IComputerAccess#queueEvent(String, Object[])
+ */
+ @Nonnull
+ public static MethodResult pullEvent( @Nullable String filter, @Nonnull ILuaCallback callback )
+ {
+ Objects.requireNonNull( callback, "callback cannot be null" );
+ return new MethodResult( new Object[] { filter }, results -> {
+ if( results.length >= 1 && results[0].equals( "terminate" ) ) throw new LuaException( "Terminated", 0 );
+ return callback.resume( results );
+ } );
+ }
+
+ /**
+ * The same as {@link #pullEvent(String, ILuaCallback)}, except "terminated" events are ignored. Only use this if
+ * you want to prevent program termination, which is not recommended. This method is exactly equivalent to
+ * {@code os.pullEventRaw()} in Lua.
+ *
+ * @param filter A specific event to wait for, or null to wait for any event.
+ * @param callback The callback to resume with the name of the event that occurred, and any event parameters.
+ * @return The method result which represents this yield.
+ * @see #pullEvent(String, ILuaCallback)
+ */
+ @Nonnull
+ public static MethodResult pullEventRaw( @Nullable String filter, @Nonnull ILuaCallback callback )
+ {
+ Objects.requireNonNull( callback, "callback cannot be null" );
+ return new MethodResult( new Object[] { filter }, callback );
+ }
+
+ /**
+ * Yield the current coroutine with some arguments until it is resumed. This method is exactly equivalent to
+ * {@code coroutine.yield()} in lua. Use {@code pullEvent()} if you wish to wait for events.
+ *
+ * @param arguments An object array containing the arguments to pass to coroutine.yield()
+ * @param callback The callback to resume with an array containing the return values from coroutine.yield()
+ * @return The method result which represents this yield.
+ * @see #pullEvent(String, ILuaCallback)
+ */
+ @Nonnull
+ public static MethodResult yield( @Nullable Object[] arguments, @Nonnull ILuaCallback callback )
+ {
+ Objects.requireNonNull( callback, "callback cannot be null" );
+ return new MethodResult( arguments, callback );
+ }
+
+ @Nullable
+ public Object[] getResult()
+ {
+ return result;
+ }
+
+ @Nullable
+ public ILuaCallback getCallback()
+ {
+ return callback;
+ }
+
+ public int getErrorAdjust()
+ {
+ return adjust;
+ }
+
+ /**
+ * Increase the Lua error by a specific amount. One should never need to use this function - it largely exists for
+ * some CC internal code.
+ *
+ * @param adjust The amount to increase the level by.
+ * @return The new {@link MethodResult} with an adjusted error. This has no effect on immediate results.
+ */
+ @Nonnull
+ public MethodResult adjustError( int adjust )
+ {
+ if( adjust < 0 ) throw new IllegalArgumentException( "cannot adjust by a negative amount" );
+ if( adjust == 0 || callback == null ) return this;
+ return new MethodResult( result, callback, this.adjust + adjust );
+ }
+}
diff --git a/src/main/java/dan200/computercraft/api/lua/ObjectArguments.java b/src/main/java/dan200/computercraft/api/lua/ObjectArguments.java
new file mode 100644
index 000000000..4ccc50b89
--- /dev/null
+++ b/src/main/java/dan200/computercraft/api/lua/ObjectArguments.java
@@ -0,0 +1,66 @@
+/*
+ * This file is part of the public ComputerCraft API - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
+ * For help using the API, and posting your mods, visit the forums at computercraft.info.
+ */
+package dan200.computercraft.api.lua;
+
+import javax.annotation.Nullable;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * An implementation of {@link IArguments} which wraps an array of {@link Object}.
+ */
+public final class ObjectArguments implements IArguments
+{
+ private static final IArguments EMPTY = new ObjectArguments();
+ private final List args;
+
+ @Deprecated
+ @SuppressWarnings( "unused" )
+ public ObjectArguments( IArguments arguments )
+ {
+ throw new IllegalStateException();
+ }
+
+ public ObjectArguments( Object... args )
+ {
+ this.args = Arrays.asList( args );
+ }
+
+ public ObjectArguments( List args )
+ {
+ this.args = Objects.requireNonNull( args );
+ }
+
+ @Override
+ public int count()
+ {
+ return args.size();
+ }
+
+ @Override
+ public IArguments drop( int count )
+ {
+ if( count < 0 ) throw new IllegalStateException( "count cannot be negative" );
+ if( count == 0 ) return this;
+ if( count >= args.size() ) return EMPTY;
+
+ return new ObjectArguments( args.subList( count, args.size() ) );
+ }
+
+ @Nullable
+ @Override
+ public Object get( int index )
+ {
+ return index >= args.size() ? null : args.get( index );
+ }
+
+ @Override
+ public Object[] getAll()
+ {
+ return args.toArray();
+ }
+}
diff --git a/src/main/java/dan200/computercraft/api/peripheral/IComputerAccess.java b/src/main/java/dan200/computercraft/api/peripheral/IComputerAccess.java
index 9a6f243e9..fd46dd056 100644
--- a/src/main/java/dan200/computercraft/api/peripheral/IComputerAccess.java
+++ b/src/main/java/dan200/computercraft/api/peripheral/IComputerAccess.java
@@ -8,8 +8,10 @@
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
+import dan200.computercraft.api.lua.ILuaCallback;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaTask;
+import dan200.computercraft.api.lua.MethodResult;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
@@ -146,9 +148,9 @@ default String mountWritable( @Nonnull String desiredLocation, @Nonnull IWritabl
*
* You may supply {@code null} to indicate that no arguments are to be supplied.
* @throws NotAttachedException If the peripheral has been detached.
- * @see IPeripheral#callMethod
+ * @see MethodResult#pullEvent(String, ILuaCallback)
*/
- void queueEvent( @Nonnull String event, @Nullable Object[] arguments );
+ void queueEvent( @Nonnull String event, @Nullable Object... arguments );
/**
* Get a string, unique to the computer, by which the computer refers to this peripheral.
diff --git a/src/main/java/dan200/computercraft/api/peripheral/IDynamicPeripheral.java b/src/main/java/dan200/computercraft/api/peripheral/IDynamicPeripheral.java
new file mode 100644
index 000000000..5e7e8bfcc
--- /dev/null
+++ b/src/main/java/dan200/computercraft/api/peripheral/IDynamicPeripheral.java
@@ -0,0 +1,53 @@
+/*
+ * This file is part of the public ComputerCraft API - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
+ * For help using the API, and posting your mods, visit the forums at computercraft.info.
+ */
+package dan200.computercraft.api.peripheral;
+
+import dan200.computercraft.api.lua.*;
+
+import javax.annotation.Nonnull;
+
+/**
+ * A peripheral whose methods are not known at runtime.
+ *
+ * This behaves similarly to {@link IDynamicLuaObject}, though also accepting the current {@link IComputerAccess}.
+ * Generally one may use {@link LuaFunction} instead of implementing this interface.
+ */
+public interface IDynamicPeripheral extends IPeripheral
+{
+ /**
+ * Should return an array of strings that identify the methods that this peripheral exposes to Lua. This will be
+ * called once before each attachment, and should not change when called multiple times.
+ *
+ * @return An array of strings representing method names.
+ * @see #callMethod
+ */
+ @Nonnull
+ String[] getMethodNames();
+
+ /**
+ * This is called when a lua program on an attached computer calls {@code peripheral.call()} with
+ * one of the methods exposed by {@link #getMethodNames()}.
+ *
+ * Be aware that this will be called from the ComputerCraft Lua thread, and must be thread-safe when interacting
+ * with Minecraft objects.
+ *
+ * @param computer The interface to the computer that is making the call. Remember that multiple
+ * computers can be attached to a peripheral at once.
+ * @param context The context of the currently running lua thread. This can be used to wait for events
+ * or otherwise yield.
+ * @param method An integer identifying which of the methods from getMethodNames() the computercraft
+ * wishes to call. The integer indicates the index into the getMethodNames() table
+ * that corresponds to the string passed into peripheral.call()
+ * @param arguments The arguments for this method.
+ * @return A {@link MethodResult} containing the values to return or the action to perform.
+ * @throws LuaException If you throw any exception from this function, a lua error will be raised with the
+ * same message as your exception. Use this to throw appropriate errors if the wrong
+ * arguments are supplied to your method.
+ * @see #getMethodNames()
+ */
+ @Nonnull
+ MethodResult callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull IArguments arguments ) throws LuaException;
+}
diff --git a/src/main/java/dan200/computercraft/api/peripheral/IPeripheral.java b/src/main/java/dan200/computercraft/api/peripheral/IPeripheral.java
index 310ff24c5..a361848c3 100644
--- a/src/main/java/dan200/computercraft/api/peripheral/IPeripheral.java
+++ b/src/main/java/dan200/computercraft/api/peripheral/IPeripheral.java
@@ -5,15 +5,17 @@
*/
package dan200.computercraft.api.peripheral;
-import dan200.computercraft.api.lua.ArgumentHelper;
-import dan200.computercraft.api.lua.ILuaContext;
-import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.LuaFunction;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
- * The interface that defines a peripheral. See {@link IPeripheralProvider} for how to associate blocks with peripherals.
+ * The interface that defines a peripheral. See {@link IPeripheralProvider} for how to associate blocks with
+ * peripherals.
+ *
+ * Peripherals should provide a series of methods to the user, either using {@link LuaFunction} or by implementing
+ * {@link IDynamicPeripheral}.
*/
public interface IPeripheral
{
@@ -26,57 +28,6 @@ public interface IPeripheral
@Nonnull
String getType();
- /**
- * Should return an array of strings that identify the methods that this
- * peripheral exposes to Lua. This will be called once before each attachment,
- * and should not change when called multiple times.
- *
- * @return An array of strings representing method names.
- * @see #callMethod
- */
- @Nonnull
- String[] getMethodNames();
-
- /**
- * This is called when a lua program on an attached computer calls {@code peripheral.call()} with
- * one of the methods exposed by {@link #getMethodNames()}.
- *
- * Be aware that this will be called from the ComputerCraft Lua thread, and must be thread-safe when interacting
- * with Minecraft objects.
- *
- * @param computer The interface to the computer that is making the call. Remember that multiple
- * computers can be attached to a peripheral at once.
- * @param context The context of the currently running lua thread. This can be used to wait for events
- * or otherwise yield.
- * @param method An integer identifying which of the methods from getMethodNames() the computercraft
- * wishes to call. The integer indicates the index into the getMethodNames() table
- * that corresponds to the string passed into peripheral.call()
- * @param arguments An array of objects, representing the arguments passed into {@code peripheral.call()}.
- * Lua values of type "string" will be represented by Object type String.
- * Lua values of type "number" will be represented by Object type Double.
- * Lua values of type "boolean" will be represented by Object type Boolean.
- * Lua values of type "table" will be represented by Object type Map.
- * Lua values of any other type will be represented by a null object.
- * This array will be empty if no arguments are passed.
- *
- * It is recommended you use {@link ArgumentHelper} in order to validate and process arguments.
- * @return An array of objects, representing values you wish to return to the lua program. Integers, Doubles, Floats,
- * Strings, Booleans, Maps, ILuaObject and null be converted to their corresponding lua type. All other types will
- * be converted to nil.
- *
- * You may return null to indicate no values should be returned.
- * @throws LuaException If you throw any exception from this function, a lua error will be raised with the
- * same message as your exception. Use this to throw appropriate errors if the wrong
- * arguments are supplied to your method.
- * @throws InterruptedException If the user shuts down or reboots the computer the coroutine is suspended,
- * InterruptedException will be thrown. This exception must not be caught or
- * intercepted, or the computer will leak memory and end up in a broken state.
- * @see #getMethodNames
- * @see ArgumentHelper
- */
- @Nullable
- Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException;
-
/**
* Is called when when a computer is attaching to the peripheral.
*
diff --git a/src/main/java/dan200/computercraft/api/peripheral/NotAttachedException.java b/src/main/java/dan200/computercraft/api/peripheral/NotAttachedException.java
index 08c343303..a01efa5a6 100644
--- a/src/main/java/dan200/computercraft/api/peripheral/NotAttachedException.java
+++ b/src/main/java/dan200/computercraft/api/peripheral/NotAttachedException.java
@@ -15,7 +15,7 @@ public class NotAttachedException extends IllegalStateException
public NotAttachedException()
{
- super( "You are not attached to this Computer" );
+ super( "You are not attached to this computer" );
}
public NotAttachedException( String s )
diff --git a/src/main/java/dan200/computercraft/api/turtle/ITurtleAccess.java b/src/main/java/dan200/computercraft/api/turtle/ITurtleAccess.java
index 6e33e2871..53b176dbf 100644
--- a/src/main/java/dan200/computercraft/api/turtle/ITurtleAccess.java
+++ b/src/main/java/dan200/computercraft/api/turtle/ITurtleAccess.java
@@ -6,8 +6,8 @@
package dan200.computercraft.api.turtle;
import com.mojang.authlib.GameProfile;
-import dan200.computercraft.api.lua.ILuaContext;
-import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.ILuaCallback;
+import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.peripheral.IPeripheral;
import net.minecraft.inventory.IInventory;
import net.minecraft.nbt.CompoundNBT;
@@ -228,21 +228,15 @@ public interface ITurtleAccess
* be supplied as a parameter to a "turtle_response" event issued to the turtle after the command has completed. Look at the
* lua source code for "rom/apis/turtle" for how to build a lua wrapper around this functionality.
*
- * @param context The Lua context to pull events from.
* @param command An object which will execute the custom command when its point in the queue is reached
* @return The objects the command returned when executed. you should probably return these to the player
* unchanged if called from a peripheral method.
* @throws UnsupportedOperationException When attempting to execute a command on the client side.
- * @throws LuaException If the user presses CTRL+T to terminate the current program while {@code executeCommand()} is
- * waiting for an event, a "Terminated" exception will be thrown here.
- * @throws InterruptedException If the user shuts down or reboots the computer while pullEvent() is waiting for an
- * event, InterruptedException will be thrown. This exception must not be caught or
- * intercepted, or the computer will leak memory and end up in a broken state.
* @see ITurtleCommand
- * @see ILuaContext#pullEvent(String)
+ * @see MethodResult#pullEvent(String, ILuaCallback)
*/
@Nonnull
- Object[] executeCommand( @Nonnull ILuaContext context, @Nonnull ITurtleCommand command ) throws LuaException, InterruptedException;
+ MethodResult executeCommand( @Nonnull ITurtleCommand command );
/**
* Start playing a specific animation. This will prevent other turtle commands from executing until
diff --git a/src/main/java/dan200/computercraft/api/turtle/ITurtleCommand.java b/src/main/java/dan200/computercraft/api/turtle/ITurtleCommand.java
index 88b4b7ab5..00c17ab54 100644
--- a/src/main/java/dan200/computercraft/api/turtle/ITurtleCommand.java
+++ b/src/main/java/dan200/computercraft/api/turtle/ITurtleCommand.java
@@ -5,14 +5,12 @@
*/
package dan200.computercraft.api.turtle;
-import dan200.computercraft.api.lua.ILuaContext;
-
import javax.annotation.Nonnull;
/**
- * An interface for objects executing custom turtle commands, used with {@link ITurtleAccess#executeCommand(ILuaContext, ITurtleCommand)}.
+ * An interface for objects executing custom turtle commands, used with {@link ITurtleAccess#executeCommand(ITurtleCommand)}.
*
- * @see ITurtleAccess#executeCommand(ILuaContext, ITurtleCommand)
+ * @see ITurtleAccess#executeCommand(ITurtleCommand)
*/
@FunctionalInterface
public interface ITurtleCommand
@@ -25,7 +23,7 @@ public interface ITurtleCommand
*
* @param turtle Access to the turtle for whom the command was issued.
* @return A result, indicating whether this action succeeded or not.
- * @see ITurtleAccess#executeCommand(ILuaContext, ITurtleCommand)
+ * @see ITurtleAccess#executeCommand(ITurtleCommand)
* @see TurtleCommandResult#success()
* @see TurtleCommandResult#failure(String)
* @see TurtleCommandResult
diff --git a/src/main/java/dan200/computercraft/api/turtle/event/TurtleBlockEvent.java b/src/main/java/dan200/computercraft/api/turtle/event/TurtleBlockEvent.java
index e20ab37b1..efd20fd36 100644
--- a/src/main/java/dan200/computercraft/api/turtle/event/TurtleBlockEvent.java
+++ b/src/main/java/dan200/computercraft/api/turtle/event/TurtleBlockEvent.java
@@ -5,8 +5,7 @@
*/
package dan200.computercraft.api.turtle.event;
-import dan200.computercraft.api.lua.ILuaContext;
-import dan200.computercraft.api.peripheral.IComputerAccess;
+import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
@@ -223,7 +222,7 @@ public Map getData()
* Add new information to the inspection result. Note this will override fields with the same name.
*
* @param newData The data to add. Note all values should be convertible to Lua (see
- * {@link dan200.computercraft.api.peripheral.IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}).
+ * {@link MethodResult#of(Object)}).
*/
public void addData( @Nonnull Map newData )
{
diff --git a/src/main/java/dan200/computercraft/api/turtle/event/TurtleInspectItemEvent.java b/src/main/java/dan200/computercraft/api/turtle/event/TurtleInspectItemEvent.java
index 12a1b751b..f21756a1a 100644
--- a/src/main/java/dan200/computercraft/api/turtle/event/TurtleInspectItemEvent.java
+++ b/src/main/java/dan200/computercraft/api/turtle/event/TurtleInspectItemEvent.java
@@ -5,8 +5,7 @@
*/
package dan200.computercraft.api.turtle.event;
-import dan200.computercraft.api.lua.ILuaContext;
-import dan200.computercraft.api.peripheral.IComputerAccess;
+import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.turtle.ITurtleAccess;
import net.minecraft.item.ItemStack;
@@ -63,7 +62,7 @@ public Map getData()
* Add new information to the inspection result. Note this will override fields with the same name.
*
* @param newData The data to add. Note all values should be convertible to Lua (see
- * {@link dan200.computercraft.api.peripheral.IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}).
+ * {@link MethodResult#of(Object)}).
*/
public void addData( @Nonnull Map newData )
{
diff --git a/src/main/java/dan200/computercraft/core/apis/ComputerAccess.java b/src/main/java/dan200/computercraft/core/apis/ComputerAccess.java
index 019333500..1eebc5e01 100644
--- a/src/main/java/dan200/computercraft/core/apis/ComputerAccess.java
+++ b/src/main/java/dan200/computercraft/core/apis/ComputerAccess.java
@@ -116,7 +116,7 @@ public int getID()
}
@Override
- public void queueEvent( @Nonnull final String event, final Object[] arguments )
+ public void queueEvent( @Nonnull String event, Object... arguments )
{
Objects.requireNonNull( event, "event cannot be null" );
m_environment.queueEvent( event, arguments );
diff --git a/src/main/java/dan200/computercraft/core/apis/FSAPI.java b/src/main/java/dan200/computercraft/core/apis/FSAPI.java
index 90a866a53..f9f45a243 100644
--- a/src/main/java/dan200/computercraft/core/apis/FSAPI.java
+++ b/src/main/java/dan200/computercraft/core/apis/FSAPI.java
@@ -6,8 +6,8 @@
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.ILuaAPI;
-import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
import dan200.computercraft.core.apis.handles.BinaryWritableHandle;
import dan200.computercraft.core.apis.handles.EncodedReadableHandle;
@@ -17,7 +17,6 @@
import dan200.computercraft.core.filesystem.FileSystemWrapper;
import dan200.computercraft.core.tracking.TrackingField;
-import javax.annotation.Nonnull;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.nio.channels.ReadableByteChannel;
@@ -29,17 +28,14 @@
import java.util.OptionalLong;
import java.util.function.Function;
-import static dan200.computercraft.api.lua.ArgumentHelper.getString;
-
public class FSAPI implements ILuaAPI
{
- private IAPIEnvironment m_env;
- private FileSystem m_fileSystem;
+ private final IAPIEnvironment environment;
+ private FileSystem fileSystem = null;
public FSAPI( IAPIEnvironment env )
{
- m_env = env;
- m_fileSystem = null;
+ environment = env;
}
@Override
@@ -51,329 +47,280 @@ public String[] getNames()
@Override
public void startup()
{
- m_fileSystem = m_env.getFileSystem();
+ fileSystem = environment.getFileSystem();
}
@Override
public void shutdown()
{
- m_fileSystem = null;
+ fileSystem = null;
}
- @Nonnull
- @Override
- public String[] getMethodNames()
+ @LuaFunction
+ public final String[] list( String path ) throws LuaException
{
- return new String[] {
- "list",
- "combine",
- "getName",
- "getSize",
- "exists",
- "isDir",
- "isReadOnly",
- "makeDir",
- "move",
- "copy",
- "delete",
- "open",
- "getDrive",
- "getFreeSpace",
- "find",
- "getDir",
- "getCapacity",
- "attributes",
- };
- }
-
- @Override
- public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
- {
- switch( method )
+ environment.addTrackingChange( TrackingField.FS_OPS );
+ try
{
- case 0:
+ return fileSystem.list( path );
+ }
+ catch( FileSystemException e )
+ {
+ throw new LuaException( e.getMessage() );
+ }
+ }
+
+ @LuaFunction
+ public final String combine( String pathA, String pathB )
+ {
+ return fileSystem.combine( pathA, pathB );
+ }
+
+ @LuaFunction
+ public final String getName( String path )
+ {
+ return FileSystem.getName( path );
+ }
+
+ @LuaFunction
+ public final String getDir( String path )
+ {
+ return FileSystem.getDirectory( path );
+ }
+
+ @LuaFunction
+ public final long getSize( String path ) throws LuaException
+ {
+ try
+ {
+ return fileSystem.getSize( path );
+ }
+ catch( FileSystemException e )
+ {
+ throw new LuaException( e.getMessage() );
+ }
+ }
+
+ @LuaFunction
+ public final boolean exists( String path )
+ {
+ try
+ {
+ return fileSystem.exists( path );
+ }
+ catch( FileSystemException e )
+ {
+ return false;
+ }
+ }
+
+ @LuaFunction
+ public final boolean isDir( String path )
+ {
+ try
+ {
+ return fileSystem.isDir( path );
+ }
+ catch( FileSystemException e )
+ {
+ return false;
+ }
+ }
+
+ @LuaFunction
+ public final boolean isReadOnly( String path )
+ {
+ try
+ {
+ return fileSystem.isReadOnly( path );
+ }
+ catch( FileSystemException e )
+ {
+ return false;
+ }
+ }
+
+ @LuaFunction
+ public final void makeDir( String path ) throws LuaException
+ {
+ try
+ {
+ environment.addTrackingChange( TrackingField.FS_OPS );
+ fileSystem.makeDir( path );
+ }
+ catch( FileSystemException e )
+ {
+ throw new LuaException( e.getMessage() );
+ }
+ }
+
+ @LuaFunction
+ public final void move( String path, String dest ) throws LuaException
+ {
+ try
+ {
+ environment.addTrackingChange( TrackingField.FS_OPS );
+ fileSystem.move( path, dest );
+ }
+ catch( FileSystemException e )
+ {
+ throw new LuaException( e.getMessage() );
+ }
+ }
+
+ @LuaFunction
+ public final void copy( String path, String dest ) throws LuaException
+ {
+ try
+ {
+ environment.addTrackingChange( TrackingField.FS_OPS );
+ fileSystem.copy( path, dest );
+ }
+ catch( FileSystemException e )
+ {
+ throw new LuaException( e.getMessage() );
+ }
+ }
+
+ @LuaFunction
+ public final void delete( String path ) throws LuaException
+ {
+ try
+ {
+ environment.addTrackingChange( TrackingField.FS_OPS );
+ fileSystem.delete( path );
+ }
+ catch( FileSystemException e )
+ {
+ throw new LuaException( e.getMessage() );
+ }
+ }
+
+ @LuaFunction
+ public final Object[] open( String path, String mode ) throws LuaException
+ {
+ environment.addTrackingChange( TrackingField.FS_OPS );
+ try
+ {
+ switch( mode )
{
- // list
- String path = getString( args, 0 );
- m_env.addTrackingChange( TrackingField.FS_OPS );
- try
+ case "r":
{
- return new Object[] { m_fileSystem.list( path ) };
+ // Open the file for reading, then create a wrapper around the reader
+ FileSystemWrapper reader = fileSystem.openForRead( path, EncodedReadableHandle::openUtf8 );
+ return new Object[] { new EncodedReadableHandle( reader.get(), reader ) };
}
- catch( FileSystemException e )
+ case "w":
{
- throw new LuaException( e.getMessage() );
+ // Open the file for writing, then create a wrapper around the writer
+ FileSystemWrapper writer = fileSystem.openForWrite( path, false, EncodedWritableHandle::openUtf8 );
+ return new Object[] { new EncodedWritableHandle( writer.get(), writer ) };
}
+ case "a":
+ {
+ // Open the file for appending, then create a wrapper around the writer
+ FileSystemWrapper writer = fileSystem.openForWrite( path, true, EncodedWritableHandle::openUtf8 );
+ return new Object[] { new EncodedWritableHandle( writer.get(), writer ) };
+ }
+ case "rb":
+ {
+ // Open the file for binary reading, then create a wrapper around the reader
+ FileSystemWrapper reader = fileSystem.openForRead( path, Function.identity() );
+ return new Object[] { BinaryReadableHandle.of( reader.get(), reader ) };
+ }
+ case "wb":
+ {
+ // Open the file for binary writing, then create a wrapper around the writer
+ FileSystemWrapper writer = fileSystem.openForWrite( path, false, Function.identity() );
+ return new Object[] { BinaryWritableHandle.of( writer.get(), writer ) };
+ }
+ case "ab":
+ {
+ // Open the file for binary appending, then create a wrapper around the reader
+ FileSystemWrapper writer = fileSystem.openForWrite( path, true, Function.identity() );
+ return new Object[] { BinaryWritableHandle.of( writer.get(), writer ) };
+ }
+ default:
+ throw new LuaException( "Unsupported mode" );
}
- case 1:
- {
- // combine
- String pathA = getString( args, 0 );
- String pathB = getString( args, 1 );
- return new Object[] { m_fileSystem.combine( pathA, pathB ) };
- }
- case 2:
- {
- // getName
- String path = getString( args, 0 );
- return new Object[] { FileSystem.getName( path ) };
- }
- case 3:
- {
- // getSize
- String path = getString( args, 0 );
- try
- {
- return new Object[] { m_fileSystem.getSize( path ) };
- }
- catch( FileSystemException e )
- {
- throw new LuaException( e.getMessage() );
- }
- }
- case 4:
- {
- // exists
- String path = getString( args, 0 );
- try
- {
- return new Object[] { m_fileSystem.exists( path ) };
- }
- catch( FileSystemException e )
- {
- return new Object[] { false };
- }
- }
- case 5:
- {
- // isDir
- String path = getString( args, 0 );
- try
- {
- return new Object[] { m_fileSystem.isDir( path ) };
- }
- catch( FileSystemException e )
- {
- return new Object[] { false };
- }
- }
- case 6:
- {
- // isReadOnly
- String path = getString( args, 0 );
- try
- {
- return new Object[] { m_fileSystem.isReadOnly( path ) };
- }
- catch( FileSystemException e )
- {
- return new Object[] { false };
- }
- }
- case 7:
- {
- // makeDir
- String path = getString( args, 0 );
- try
- {
- m_env.addTrackingChange( TrackingField.FS_OPS );
- m_fileSystem.makeDir( path );
- return null;
- }
- catch( FileSystemException e )
- {
- throw new LuaException( e.getMessage() );
- }
- }
- case 8:
- {
- // move
- String path = getString( args, 0 );
- String dest = getString( args, 1 );
- try
- {
- m_env.addTrackingChange( TrackingField.FS_OPS );
- m_fileSystem.move( path, dest );
- return null;
- }
- catch( FileSystemException e )
- {
- throw new LuaException( e.getMessage() );
- }
- }
- case 9:
- {
- // copy
- String path = getString( args, 0 );
- String dest = getString( args, 1 );
- try
- {
- m_env.addTrackingChange( TrackingField.FS_OPS );
- m_fileSystem.copy( path, dest );
- return null;
- }
- catch( FileSystemException e )
- {
- throw new LuaException( e.getMessage() );
- }
- }
- case 10:
- {
- // delete
- String path = getString( args, 0 );
- try
- {
- m_env.addTrackingChange( TrackingField.FS_OPS );
- m_fileSystem.delete( path );
- return null;
- }
- catch( FileSystemException e )
- {
- throw new LuaException( e.getMessage() );
- }
- }
- case 11:
- {
- // open
- String path = getString( args, 0 );
- String mode = getString( args, 1 );
- m_env.addTrackingChange( TrackingField.FS_OPS );
- try
- {
- switch( mode )
- {
- case "r":
- {
- // Open the file for reading, then create a wrapper around the reader
- FileSystemWrapper reader = m_fileSystem.openForRead( path, EncodedReadableHandle::openUtf8 );
- return new Object[] { new EncodedReadableHandle( reader.get(), reader ) };
- }
- case "w":
- {
- // Open the file for writing, then create a wrapper around the writer
- FileSystemWrapper writer = m_fileSystem.openForWrite( path, false, EncodedWritableHandle::openUtf8 );
- return new Object[] { new EncodedWritableHandle( writer.get(), writer ) };
- }
- case "a":
- {
- // Open the file for appending, then create a wrapper around the writer
- FileSystemWrapper writer = m_fileSystem.openForWrite( path, true, EncodedWritableHandle::openUtf8 );
- return new Object[] { new EncodedWritableHandle( writer.get(), writer ) };
- }
- case "rb":
- {
- // Open the file for binary reading, then create a wrapper around the reader
- FileSystemWrapper reader = m_fileSystem.openForRead( path, Function.identity() );
- return new Object[] { new BinaryReadableHandle( reader.get(), reader ) };
- }
- case "wb":
- {
- // Open the file for binary writing, then create a wrapper around the writer
- FileSystemWrapper writer = m_fileSystem.openForWrite( path, false, Function.identity() );
- return new Object[] { new BinaryWritableHandle( writer.get(), writer ) };
- }
- case "ab":
- {
- // Open the file for binary appending, then create a wrapper around the reader
- FileSystemWrapper writer = m_fileSystem.openForWrite( path, true, Function.identity() );
- return new Object[] { new BinaryWritableHandle( writer.get(), writer ) };
- }
- default:
- throw new LuaException( "Unsupported mode" );
- }
- }
- catch( FileSystemException e )
- {
- return new Object[] { null, e.getMessage() };
- }
- }
- case 12:
- {
- // getDrive
- String path = getString( args, 0 );
- try
- {
- if( !m_fileSystem.exists( path ) )
- {
- return null;
- }
- return new Object[] { m_fileSystem.getMountLabel( path ) };
- }
- catch( FileSystemException e )
- {
- throw new LuaException( e.getMessage() );
- }
- }
- case 13:
- {
- // getFreeSpace
- String path = getString( args, 0 );
- try
- {
- long freeSpace = m_fileSystem.getFreeSpace( path );
- if( freeSpace >= 0 )
- {
- return new Object[] { freeSpace };
- }
- return new Object[] { "unlimited" };
- }
- catch( FileSystemException e )
- {
- throw new LuaException( e.getMessage() );
- }
- }
- case 14: // find
- {
- String path = getString( args, 0 );
- try
- {
- m_env.addTrackingChange( TrackingField.FS_OPS );
- return new Object[] { m_fileSystem.find( path ) };
- }
- catch( FileSystemException e )
- {
- throw new LuaException( e.getMessage() );
- }
- }
- case 15: // getDir
- {
- String path = getString( args, 0 );
- return new Object[] { FileSystem.getDirectory( path ) };
- }
- case 16: // getCapacity
- {
- String path = getString( args, 0 );
- try
- {
- OptionalLong capacity = m_fileSystem.getCapacity( path );
- return new Object[] { capacity.isPresent() ? capacity.getAsLong() : null };
- }
- catch( FileSystemException e )
- {
- throw new LuaException( e.getMessage() );
- }
- }
- case 17: // attributes
- {
- String path = getString( args, 0 );
- try
- {
- BasicFileAttributes attributes = m_fileSystem.getAttributes( path );
- Map result = new HashMap<>();
- result.put( "modification", getFileTime( attributes.lastModifiedTime() ) );
- result.put( "created", getFileTime( attributes.creationTime() ) );
- result.put( "size", attributes.isDirectory() ? 0 : attributes.size() );
- result.put( "isDir", attributes.isDirectory() );
- return new Object[] { result };
- }
- catch( FileSystemException e )
- {
- throw new LuaException( e.getMessage() );
- }
- }
- default:
- assert false;
- return null;
+ }
+ catch( FileSystemException e )
+ {
+ return new Object[] { null, e.getMessage() };
+ }
+ }
+
+ @LuaFunction
+ public final Object[] getDrive( String path ) throws LuaException
+ {
+ try
+ {
+ return fileSystem.exists( path ) ? new Object[] { fileSystem.getMountLabel( path ) } : null;
+ }
+ catch( FileSystemException e )
+ {
+ throw new LuaException( e.getMessage() );
+ }
+ }
+
+ @LuaFunction
+ public final Object getFreeSpace( String path ) throws LuaException
+ {
+ try
+ {
+ long freeSpace = fileSystem.getFreeSpace( path );
+ return freeSpace >= 0 ? freeSpace : "unlimited";
+ }
+ catch( FileSystemException e )
+ {
+ throw new LuaException( e.getMessage() );
+ }
+ }
+
+ @LuaFunction
+ public final String[] find( String path ) throws LuaException
+ {
+ try
+ {
+ environment.addTrackingChange( TrackingField.FS_OPS );
+ return fileSystem.find( path );
+ }
+ catch( FileSystemException e )
+ {
+ throw new LuaException( e.getMessage() );
+ }
+ }
+
+ @LuaFunction
+ public final Object getCapacity( String path ) throws LuaException
+ {
+ try
+ {
+ OptionalLong capacity = fileSystem.getCapacity( path );
+ return capacity.isPresent() ? capacity.getAsLong() : null;
+ }
+ catch( FileSystemException e )
+ {
+ throw new LuaException( e.getMessage() );
+ }
+ }
+
+ @LuaFunction
+ public final Map attributes( String path ) throws LuaException
+ {
+ try
+ {
+ BasicFileAttributes attributes = fileSystem.getAttributes( path );
+ Map result = new HashMap<>();
+ result.put( "modification", getFileTime( attributes.lastModifiedTime() ) );
+ result.put( "created", getFileTime( attributes.creationTime() ) );
+ result.put( "size", attributes.isDirectory() ? 0 : attributes.size() );
+ result.put( "isDir", attributes.isDirectory() );
+ return result;
+ }
+ catch( FileSystemException e )
+ {
+ throw new LuaException( e.getMessage() );
}
}
diff --git a/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java b/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java
index 375135ffd..eac31b9f8 100644
--- a/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java
+++ b/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java
@@ -6,9 +6,10 @@
package dan200.computercraft.core.apis;
import dan200.computercraft.ComputerCraft;
+import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.ILuaAPI;
-import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.apis.http.*;
import dan200.computercraft.core.apis.http.request.HttpRequest;
import dan200.computercraft.core.apis.http.websocket.Websocket;
@@ -21,8 +22,8 @@
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
+import java.util.Optional;
-import static dan200.computercraft.api.lua.ArgumentHelper.*;
import static dan200.computercraft.core.apis.TableHelper.*;
public class HTTPAPI implements ILuaAPI
@@ -68,135 +69,112 @@ public void update()
Resource.cleanup();
}
- @Nonnull
- @Override
- public String[] getMethodNames()
+ @LuaFunction
+ public final Object[] request( IArguments args ) throws LuaException
{
- return new String[] {
- "request",
- "checkURL",
- "websocket",
- };
+ String address, postString, requestMethod;
+ Map, ?> headerTable;
+ boolean binary, redirect;
+
+ if( args.get( 0 ) instanceof Map )
+ {
+ Map, ?> options = args.getTable( 0 );
+ address = getStringField( options, "url" );
+ postString = optStringField( options, "body", null );
+ headerTable = optTableField( options, "headers", Collections.emptyMap() );
+ binary = optBooleanField( options, "binary", false );
+ requestMethod = optStringField( options, "method", null );
+ redirect = optBooleanField( options, "redirect", true );
+
+ }
+ else
+ {
+ // Get URL and post information
+ address = args.getString( 0 );
+ postString = args.optString( 1, null );
+ headerTable = args.optTable( 2, Collections.emptyMap() );
+ binary = args.optBoolean( 3, false );
+ requestMethod = null;
+ redirect = true;
+ }
+
+ HttpHeaders headers = getHeaders( headerTable );
+
+ HttpMethod httpMethod;
+ if( requestMethod == null )
+ {
+ httpMethod = postString == null ? HttpMethod.GET : HttpMethod.POST;
+ }
+ else
+ {
+ httpMethod = HttpMethod.valueOf( requestMethod.toUpperCase( Locale.ROOT ) );
+ if( httpMethod == null || requestMethod.equalsIgnoreCase( "CONNECT" ) )
+ {
+ throw new LuaException( "Unsupported HTTP method" );
+ }
+ }
+
+ try
+ {
+ URI uri = HttpRequest.checkUri( address );
+ HttpRequest request = new HttpRequest( requests, m_apiEnvironment, address, postString, headers, binary, redirect );
+
+ long requestBody = request.body().readableBytes() + HttpRequest.getHeaderSize( headers );
+ if( ComputerCraft.httpMaxUpload != 0 && requestBody > ComputerCraft.httpMaxUpload )
+ {
+ throw new HTTPRequestException( "Request body is too large" );
+ }
+
+ // Make the request
+ request.queue( r -> r.request( uri, httpMethod ) );
+
+ return new Object[] { true };
+ }
+ catch( HTTPRequestException e )
+ {
+ return new Object[] { false, e.getMessage() };
+ }
}
- @Override
- @SuppressWarnings( "resource" )
- public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
+ @LuaFunction
+ public final Object[] checkURL( String address )
{
- switch( method )
+ try
{
- case 0: // request
+ URI uri = HttpRequest.checkUri( address );
+ new CheckUrl( checkUrls, m_apiEnvironment, address, uri ).queue( CheckUrl::run );
+
+ return new Object[] { true };
+ }
+ catch( HTTPRequestException e )
+ {
+ return new Object[] { false, e.getMessage() };
+ }
+ }
+
+ @LuaFunction
+ public final Object[] websocket( String address, Optional> headerTbl ) throws LuaException
+ {
+ if( !ComputerCraft.httpWebsocketEnabled )
+ {
+ throw new LuaException( "Websocket connections are disabled" );
+ }
+
+ HttpHeaders headers = getHeaders( headerTbl.orElse( Collections.emptyMap() ) );
+
+ try
+ {
+ URI uri = Websocket.checkUri( address );
+ if( !new Websocket( websockets, m_apiEnvironment, uri, address, headers ).queue( Websocket::connect ) )
{
- String address, postString, requestMethod;
- Map, ?> headerTable;
- boolean binary, redirect;
-
- if( args.length >= 1 && args[0] instanceof Map )
- {
- Map, ?> options = (Map, ?>) args[0];
- address = getStringField( options, "url" );
- postString = optStringField( options, "body", null );
- headerTable = optTableField( options, "headers", Collections.emptyMap() );
- binary = optBooleanField( options, "binary", false );
- requestMethod = optStringField( options, "method", null );
- redirect = optBooleanField( options, "redirect", true );
-
- }
- else
- {
- // Get URL and post information
- address = getString( args, 0 );
- postString = optString( args, 1, null );
- headerTable = optTable( args, 2, Collections.emptyMap() );
- binary = optBoolean( args, 3, false );
- requestMethod = null;
- redirect = true;
- }
-
- HttpHeaders headers = getHeaders( headerTable );
-
-
- HttpMethod httpMethod;
- if( requestMethod == null )
- {
- httpMethod = postString == null ? HttpMethod.GET : HttpMethod.POST;
- }
- else
- {
- httpMethod = HttpMethod.valueOf( requestMethod.toUpperCase( Locale.ROOT ) );
- if( httpMethod == null || requestMethod.equalsIgnoreCase( "CONNECT" ) )
- {
- throw new LuaException( "Unsupported HTTP method" );
- }
- }
-
- try
- {
- URI uri = HttpRequest.checkUri( address );
- HttpRequest request = new HttpRequest( requests, m_apiEnvironment, address, postString, headers, binary, redirect );
-
- long requestBody = request.body().readableBytes() + HttpRequest.getHeaderSize( headers );
- if( ComputerCraft.httpMaxUpload != 0 && requestBody > ComputerCraft.httpMaxUpload )
- {
- throw new HTTPRequestException( "Request body is too large" );
- }
-
- // Make the request
- request.queue( r -> r.request( uri, httpMethod ) );
-
- return new Object[] { true };
- }
- catch( HTTPRequestException e )
- {
- return new Object[] { false, e.getMessage() };
- }
+ throw new LuaException( "Too many websockets already open" );
}
- case 1: // checkURL
- {
- String address = getString( args, 0 );
- // Check URL
- try
- {
- URI uri = HttpRequest.checkUri( address );
- new CheckUrl( checkUrls, m_apiEnvironment, address, uri ).queue( CheckUrl::run );
-
- return new Object[] { true };
- }
- catch( HTTPRequestException e )
- {
- return new Object[] { false, e.getMessage() };
- }
- }
- case 2: // websocket
- {
- String address = getString( args, 0 );
- Map, ?> headerTbl = optTable( args, 1, Collections.emptyMap() );
-
- if( !ComputerCraft.httpWebsocketEnabled )
- {
- throw new LuaException( "Websocket connections are disabled" );
- }
-
- HttpHeaders headers = getHeaders( headerTbl );
-
- try
- {
- URI uri = Websocket.checkUri( address );
- if( !new Websocket( websockets, m_apiEnvironment, uri, address, headers ).queue( Websocket::connect ) )
- {
- throw new LuaException( "Too many websockets already open" );
- }
-
- return new Object[] { true };
- }
- catch( HTTPRequestException e )
- {
- return new Object[] { false, e.getMessage() };
- }
- }
- default:
- return null;
+ return new Object[] { true };
+ }
+ catch( HTTPRequestException e )
+ {
+ return new Object[] { false, e.getMessage() };
}
}
diff --git a/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java b/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java
index 0a4315960..19e2d2f14 100644
--- a/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java
+++ b/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java
@@ -43,7 +43,7 @@ interface IPeripheralChangeListener
void reboot();
- void queueEvent( String event, Object[] args );
+ void queueEvent( String event, Object... args );
void setOutput( ComputerSide side, int output );
diff --git a/src/main/java/dan200/computercraft/core/apis/OSAPI.java b/src/main/java/dan200/computercraft/core/apis/OSAPI.java
index 825ad05fa..1b63323ea 100644
--- a/src/main/java/dan200/computercraft/core/apis/OSAPI.java
+++ b/src/main/java/dan200/computercraft/core/apis/OSAPI.java
@@ -5,9 +5,10 @@
*/
package dan200.computercraft.core.apis;
+import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.ILuaAPI;
-import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.shared.util.StringUtil;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@@ -20,11 +21,11 @@
import java.time.format.DateTimeFormatterBuilder;
import java.util.*;
-import static dan200.computercraft.api.lua.ArgumentHelper.*;
+import static dan200.computercraft.api.lua.LuaValues.checkFinite;
public class OSAPI implements ILuaAPI
{
- private IAPIEnvironment m_apiEnvironment;
+ private final IAPIEnvironment apiEnvironment;
private final Int2ObjectMap m_alarms = new Int2ObjectOpenHashMap<>();
private int m_clock;
@@ -55,11 +56,9 @@ public int compareTo( @Nonnull Alarm o )
public OSAPI( IAPIEnvironment environment )
{
- m_apiEnvironment = environment;
+ apiEnvironment = environment;
}
- // ILuaAPI implementation
-
@Override
public String[] getNames()
{
@@ -69,8 +68,8 @@ public String[] getNames()
@Override
public void startup()
{
- m_time = m_apiEnvironment.getComputerEnvironment().getTimeOfDay();
- m_day = m_apiEnvironment.getComputerEnvironment().getDay();
+ m_time = apiEnvironment.getComputerEnvironment().getTimeOfDay();
+ m_day = apiEnvironment.getComputerEnvironment().getDay();
m_clock = 0;
synchronized( m_alarms )
@@ -89,8 +88,8 @@ public void update()
{
double previousTime = m_time;
int previousDay = m_day;
- double time = m_apiEnvironment.getComputerEnvironment().getTimeOfDay();
- int day = m_apiEnvironment.getComputerEnvironment().getDay();
+ double time = apiEnvironment.getComputerEnvironment().getTimeOfDay();
+ int day = apiEnvironment.getComputerEnvironment().getDay();
if( time > previousTime || day > previousDay )
{
@@ -103,7 +102,7 @@ public void update()
double t = alarm.m_day * 24.0 + alarm.m_time;
if( now >= t )
{
- queueLuaEvent( "alarm", new Object[] { entry.getIntKey() } );
+ apiEnvironment.queueEvent( "alarm", entry.getIntKey() );
it.remove();
}
}
@@ -123,31 +122,6 @@ public void shutdown()
}
}
- @Nonnull
- @Override
- public String[] getMethodNames()
- {
- return new String[] {
- "queueEvent",
- "startTimer",
- "setAlarm",
- "shutdown",
- "reboot",
- "computerID",
- "getComputerID",
- "setComputerLabel",
- "computerLabel",
- "getComputerLabel",
- "clock",
- "time",
- "day",
- "cancelTimer",
- "cancelAlarm",
- "epoch",
- "date",
- };
- }
-
private static float getTimeForCalendar( Calendar c )
{
float time = c.get( Calendar.HOUR_OF_DAY );
@@ -174,214 +148,174 @@ private static long getEpochForCalendar( Calendar c )
return c.getTime().getTime();
}
- @Override
- public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
+ @LuaFunction
+ public final void queueEvent( String name, IArguments args )
{
- switch( method )
+ apiEnvironment.queueEvent( name, args.drop( 1 ).getAll() );
+ }
+
+ @LuaFunction
+ public final int startTimer( double timer ) throws LuaException
+ {
+ return apiEnvironment.startTimer( Math.round( checkFinite( 0, timer ) / 0.05 ) );
+ }
+
+ @LuaFunction
+ public final void cancelTimer( int token )
+ {
+ apiEnvironment.cancelTimer( token );
+ }
+
+ @LuaFunction
+ public final int setAlarm( double time ) throws LuaException
+ {
+ checkFinite( 0, time );
+ if( time < 0.0 || time >= 24.0 ) throw new LuaException( "Number out of range" );
+ synchronized( m_alarms )
{
- case 0: // queueEvent
- queueLuaEvent( getString( args, 0 ), trimArray( args, 1 ) );
- return null;
- case 1:
- {
- // startTimer
- double timer = getFiniteDouble( args, 0 );
- int id = m_apiEnvironment.startTimer( Math.round( timer / 0.05 ) );
- return new Object[] { id };
- }
- case 2:
- {
- // setAlarm
- double time = getFiniteDouble( args, 0 );
- if( time < 0.0 || time >= 24.0 )
- {
- throw new LuaException( "Number out of range" );
- }
- synchronized( m_alarms )
- {
- int day = time > m_time ? m_day : m_day + 1;
- m_alarms.put( m_nextAlarmToken, new Alarm( time, day ) );
- return new Object[] { m_nextAlarmToken++ };
- }
- }
- case 3: // shutdown
- m_apiEnvironment.shutdown();
- return null;
- case 4: // reboot
- m_apiEnvironment.reboot();
- return null;
- case 5:
- case 6: // computerID/getComputerID
- return new Object[] { getComputerID() };
- case 7:
- {
- // setComputerLabel
- String label = optString( args, 0, null );
- m_apiEnvironment.setLabel( StringUtil.normaliseLabel( label ) );
- return null;
- }
- case 8:
- case 9:
- {
- // computerLabel/getComputerLabel
- String label = m_apiEnvironment.getLabel();
- if( label != null )
- {
- return new Object[] { label };
- }
- return null;
- }
- case 10: // clock
- return new Object[] { m_clock * 0.05 };
- case 11:
- {
- // time
- Object value = args.length > 0 ? args[0] : null;
- if( value instanceof Map ) return new Object[] { LuaDateTime.fromTable( (Map, ?>) value ) };
-
- String param = optString( args, 0, "ingame" );
- switch( param.toLowerCase( Locale.ROOT ) )
- {
- case "utc":
- {
- // Get Hour of day (UTC)
- Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
- return new Object[] { getTimeForCalendar( c ) };
- }
- case "local":
- {
- // Get Hour of day (local time)
- Calendar c = Calendar.getInstance();
- return new Object[] { getTimeForCalendar( c ) };
- }
- case "ingame":
- // Get ingame hour
- synchronized( m_alarms )
- {
- return new Object[] { m_time };
- }
- default:
- throw new LuaException( "Unsupported operation" );
- }
- }
- case 12:
- {
- // day
- String param = optString( args, 0, "ingame" );
- switch( param.toLowerCase( Locale.ROOT ) )
- {
- case "utc":
- {
- // Get numbers of days since 1970-01-01 (utc)
- Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
- return new Object[] { getDayForCalendar( c ) };
- }
- case "local":
- {
- // Get numbers of days since 1970-01-01 (local time)
- Calendar c = Calendar.getInstance();
- return new Object[] { getDayForCalendar( c ) };
- }
- case "ingame":
- // Get game day
- synchronized( m_alarms )
- {
- return new Object[] { m_day };
- }
- default:
- throw new LuaException( "Unsupported operation" );
- }
- }
- case 13:
- {
- // cancelTimer
- int token = getInt( args, 0 );
- m_apiEnvironment.cancelTimer( token );
- return null;
- }
- case 14:
- {
- // cancelAlarm
- int token = getInt( args, 0 );
- synchronized( m_alarms )
- {
- m_alarms.remove( token );
- }
- return null;
- }
- case 15: // epoch
- {
- String param = optString( args, 0, "ingame" );
- switch( param.toLowerCase( Locale.ROOT ) )
- {
- case "utc":
- {
- // Get utc epoch
- Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
- return new Object[] { getEpochForCalendar( c ) };
- }
- case "local":
- {
- // Get local epoch
- Calendar c = Calendar.getInstance();
- return new Object[] { getEpochForCalendar( c ) };
- }
- case "ingame":
- // Get in-game epoch
- synchronized( m_alarms )
- {
- return new Object[] { m_day * 86400000 + (int) (m_time * 3600000.0f) };
- }
- default:
- throw new LuaException( "Unsupported operation" );
- }
- }
- case 16: // date
- {
- String format = optString( args, 0, "%c" );
- long time = optLong( args, 1, Instant.now().getEpochSecond() );
-
- Instant instant = Instant.ofEpochSecond( time );
- ZonedDateTime date;
- ZoneOffset offset;
- if( format.startsWith( "!" ) )
- {
- offset = ZoneOffset.UTC;
- date = ZonedDateTime.ofInstant( instant, offset );
- format = format.substring( 1 );
- }
- else
- {
- ZoneId id = ZoneId.systemDefault();
- offset = id.getRules().getOffset( instant );
- date = ZonedDateTime.ofInstant( instant, id );
- }
-
- if( format.equals( "*t" ) ) return new Object[] { LuaDateTime.toTable( date, offset, instant ) };
-
- DateTimeFormatterBuilder formatter = new DateTimeFormatterBuilder();
- LuaDateTime.format( formatter, format, offset );
- return new Object[] { formatter.toFormatter( Locale.ROOT ).format( date ) };
- }
- default:
- return null;
+ int day = time > m_time ? m_day : m_day + 1;
+ m_alarms.put( m_nextAlarmToken, new Alarm( time, day ) );
+ return m_nextAlarmToken++;
}
}
- // Private methods
-
- private void queueLuaEvent( String event, Object[] args )
+ @LuaFunction
+ public final void cancelAlarm( int token )
{
- m_apiEnvironment.queueEvent( event, args );
+ synchronized( m_alarms )
+ {
+ m_alarms.remove( token );
+ }
}
- private Object[] trimArray( Object[] array, int skip )
+ @LuaFunction( "shutdown" )
+ public final void doShutdown()
{
- return Arrays.copyOfRange( array, skip, array.length );
+ apiEnvironment.shutdown();
}
- private int getComputerID()
+ @LuaFunction( "reboot" )
+ public final void doReboot()
{
- return m_apiEnvironment.getComputerID();
+ apiEnvironment.reboot();
}
+
+ @LuaFunction( { "getComputerID", "computerID" } )
+ public final int getComputerID()
+ {
+ return apiEnvironment.getComputerID();
+ }
+
+ @LuaFunction( { "getComputerLabel", "computerLabel" } )
+ public final Object[] getComputerLabel()
+ {
+ String label = apiEnvironment.getLabel();
+ return label == null ? null : new Object[] { label };
+ }
+
+ @LuaFunction
+ public final void setComputerLabel( Optional label )
+ {
+ apiEnvironment.setLabel( StringUtil.normaliseLabel( label.orElse( null ) ) );
+ }
+
+ @LuaFunction
+ public final double clock()
+ {
+ return m_clock * 0.05;
+ }
+
+ @LuaFunction
+ public final Object time( IArguments args ) throws LuaException
+ {
+ Object value = args.get( 0 );
+ if( value instanceof Map ) return LuaDateTime.fromTable( (Map, ?>) value );
+
+ String param = args.optString( 0, "ingame" );
+ switch( param.toLowerCase( Locale.ROOT ) )
+ {
+ case "utc": // Get Hour of day (UTC)
+ return getTimeForCalendar( Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ) );
+ case "local": // Get Hour of day (local time)
+ return getTimeForCalendar( Calendar.getInstance() );
+ case "ingame": // Get in-game hour
+ return m_time;
+ default:
+ throw new LuaException( "Unsupported operation" );
+ }
+ }
+
+ @LuaFunction
+ public final int day( Optional args ) throws LuaException
+ {
+ switch( args.orElse( "ingame" ).toLowerCase( Locale.ROOT ) )
+ {
+ case "utc": // Get numbers of days since 1970-01-01 (utc)
+ return getDayForCalendar( Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ) );
+ case "local": // Get numbers of days since 1970-01-01 (local time)
+ return getDayForCalendar( Calendar.getInstance() );
+ case "ingame":// Get game day
+ return m_day;
+ default:
+ throw new LuaException( "Unsupported operation" );
+ }
+ }
+
+ @LuaFunction
+ public final long epoch( Optional args ) throws LuaException
+ {
+ switch( args.orElse( "ingame" ).toLowerCase( Locale.ROOT ) )
+ {
+ case "utc":
+ {
+ // Get utc epoch
+ Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
+ return getEpochForCalendar( c );
+ }
+ case "local":
+ {
+ // Get local epoch
+ Calendar c = Calendar.getInstance();
+ return getEpochForCalendar( c );
+ }
+ case "ingame":
+ // Get in-game epoch
+ synchronized( m_alarms )
+ {
+ return m_day * 86400000 + (int) (m_time * 3600000.0f);
+ }
+ default:
+ throw new LuaException( "Unsupported operation" );
+ }
+ }
+
+ @LuaFunction
+ public final Object date( Optional formatA, Optional timeA ) throws LuaException
+ {
+ String format = formatA.orElse( "%c" );
+ long time = timeA.orElseGet( () -> Instant.now().getEpochSecond() );
+
+ Instant instant = Instant.ofEpochSecond( time );
+ ZonedDateTime date;
+ ZoneOffset offset;
+ if( format.startsWith( "!" ) )
+ {
+ offset = ZoneOffset.UTC;
+ date = ZonedDateTime.ofInstant( instant, offset );
+ format = format.substring( 1 );
+ }
+ else
+ {
+ ZoneId id = ZoneId.systemDefault();
+ offset = id.getRules().getOffset( instant );
+ date = ZonedDateTime.ofInstant( instant, id );
+ }
+
+ if( format.equals( "*t" ) ) return LuaDateTime.toTable( date, offset, instant );
+
+ DateTimeFormatterBuilder formatter = new DateTimeFormatterBuilder();
+ LuaDateTime.format( formatter, format, offset );
+ return formatter.toFormatter( Locale.ROOT ).format( date );
+ }
+
}
diff --git a/src/main/java/dan200/computercraft/core/apis/PeripheralAPI.java b/src/main/java/dan200/computercraft/core/apis/PeripheralAPI.java
index 2d07d58bc..ee37d3ba4 100644
--- a/src/main/java/dan200/computercraft/core/apis/PeripheralAPI.java
+++ b/src/main/java/dan200/computercraft/core/apis/PeripheralAPI.java
@@ -7,88 +7,74 @@
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
-import dan200.computercraft.api.lua.ILuaAPI;
-import dan200.computercraft.api.lua.ILuaContext;
-import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.*;
+import dan200.computercraft.api.peripheral.IDynamicPeripheral;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IWorkMonitor;
import dan200.computercraft.api.peripheral.NotAttachedException;
+import dan200.computercraft.core.asm.LuaMethod;
+import dan200.computercraft.core.asm.NamedMethod;
+import dan200.computercraft.core.asm.PeripheralMethod;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.tracking.TrackingField;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-import static dan200.computercraft.api.lua.ArgumentHelper.getString;
+import java.util.*;
public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChangeListener
{
private class PeripheralWrapper extends ComputerAccess
{
- private final String m_side;
- private final IPeripheral m_peripheral;
+ private final String side;
+ private final IPeripheral peripheral;
- private String m_type;
- private String[] m_methods;
- private Map m_methodMap;
- private boolean m_attached;
+ private final String type;
+ private final Map methodMap;
+ private boolean attached;
PeripheralWrapper( IPeripheral peripheral, String side )
{
- super( m_environment );
- m_side = side;
- m_peripheral = peripheral;
- m_attached = false;
+ super( environment );
+ this.side = side;
+ this.peripheral = peripheral;
+ attached = false;
- m_type = peripheral.getType();
- m_methods = peripheral.getMethodNames();
- assert m_type != null;
- assert m_methods != null;
+ type = Objects.requireNonNull( peripheral.getType(), "Peripheral type cannot be null" );
- m_methodMap = new HashMap<>();
- for( int i = 0; i < m_methods.length; i++ )
- {
- if( m_methods[i] != null )
- {
- m_methodMap.put( m_methods[i], i );
- }
- }
+ methodMap = PeripheralAPI.getMethods( peripheral );
}
public IPeripheral getPeripheral()
{
- return m_peripheral;
+ return peripheral;
}
public String getType()
{
- return m_type;
+ return type;
}
- public String[] getMethods()
+ public Collection getMethods()
{
- return m_methods;
+ return methodMap.keySet();
}
public synchronized boolean isAttached()
{
- return m_attached;
+ return attached;
}
public synchronized void attach()
{
- m_attached = true;
- m_peripheral.attach( this );
+ attached = true;
+ peripheral.attach( this );
}
public void detach()
{
// Call detach
- m_peripheral.detach( this );
+ peripheral.detach( this );
synchronized( this )
{
@@ -96,63 +82,56 @@ public void detach()
unmountAll();
}
- m_attached = false;
+ attached = false;
}
- public Object[] call( ILuaContext context, String methodName, Object[] arguments ) throws LuaException, InterruptedException
+ public MethodResult call( ILuaContext context, String methodName, IArguments arguments ) throws LuaException
{
- int method = -1;
+ PeripheralMethod method;
synchronized( this )
{
- if( m_methodMap.containsKey( methodName ) )
- {
- method = m_methodMap.get( methodName );
- }
- }
- if( method >= 0 )
- {
- m_environment.addTrackingChange( TrackingField.PERIPHERAL_OPS );
- return m_peripheral.callMethod( this, context, method, arguments );
- }
- else
- {
- throw new LuaException( "No such method " + methodName );
+ method = methodMap.get( methodName );
}
+
+ if( method == null ) throw new LuaException( "No such method " + methodName );
+
+ environment.addTrackingChange( TrackingField.PERIPHERAL_OPS );
+ return method.apply( peripheral, context, this, arguments );
}
// IComputerAccess implementation
@Override
public synchronized String mount( @Nonnull String desiredLoc, @Nonnull IMount mount, @Nonnull String driveName )
{
- if( !m_attached ) throw new NotAttachedException();
+ if( !attached ) throw new NotAttachedException();
return super.mount( desiredLoc, mount, driveName );
}
@Override
public synchronized String mountWritable( @Nonnull String desiredLoc, @Nonnull IWritableMount mount, @Nonnull String driveName )
{
- if( !m_attached ) throw new NotAttachedException();
+ if( !attached ) throw new NotAttachedException();
return super.mountWritable( desiredLoc, mount, driveName );
}
@Override
public synchronized void unmount( String location )
{
- if( !m_attached ) throw new NotAttachedException();
+ if( !attached ) throw new NotAttachedException();
super.unmount( location );
}
@Override
public int getID()
{
- if( !m_attached ) throw new NotAttachedException();
+ if( !attached ) throw new NotAttachedException();
return super.getID();
}
@Override
- public void queueEvent( @Nonnull final String event, final Object[] arguments )
+ public void queueEvent( @Nonnull String event, Object... arguments )
{
- if( !m_attached ) throw new NotAttachedException();
+ if( !attached ) throw new NotAttachedException();
super.queueEvent( event, arguments );
}
@@ -160,18 +139,18 @@ public void queueEvent( @Nonnull final String event, final Object[] arguments )
@Override
public String getAttachmentName()
{
- if( !m_attached ) throw new NotAttachedException();
- return m_side;
+ if( !attached ) throw new NotAttachedException();
+ return side;
}
@Nonnull
@Override
public Map getAvailablePeripherals()
{
- if( !m_attached ) throw new NotAttachedException();
+ if( !attached ) throw new NotAttachedException();
Map peripherals = new HashMap<>();
- for( PeripheralWrapper wrapper : m_peripherals )
+ for( PeripheralWrapper wrapper : PeripheralAPI.this.peripherals )
{
if( wrapper != null && wrapper.isAttached() )
{
@@ -186,9 +165,9 @@ public Map getAvailablePeripherals()
@Override
public IPeripheral getAvailablePeripheral( @Nonnull String name )
{
- if( !m_attached ) throw new NotAttachedException();
+ if( !attached ) throw new NotAttachedException();
- for( PeripheralWrapper wrapper : m_peripherals )
+ for( PeripheralWrapper wrapper : peripherals )
{
if( wrapper != null && wrapper.isAttached() && wrapper.getAttachmentName().equals( name ) )
{
@@ -202,27 +181,20 @@ public IPeripheral getAvailablePeripheral( @Nonnull String name )
@Override
public IWorkMonitor getMainThreadMonitor()
{
- if( !m_attached ) throw new NotAttachedException();
+ if( !attached ) throw new NotAttachedException();
return super.getMainThreadMonitor();
}
}
- private final IAPIEnvironment m_environment;
- private final PeripheralWrapper[] m_peripherals;
- private boolean m_running;
+ private final IAPIEnvironment environment;
+ private final PeripheralWrapper[] peripherals = new PeripheralWrapper[6];
+ private boolean running;
public PeripheralAPI( IAPIEnvironment environment )
{
- m_environment = environment;
- m_environment.setPeripheralChangeListener( this );
-
- m_peripherals = new PeripheralWrapper[6];
- for( int i = 0; i < 6; i++ )
- {
- m_peripherals[i] = null;
- }
-
- m_running = false;
+ this.environment = environment;
+ this.environment.setPeripheralChangeListener( this );
+ running = false;
}
// IPeripheralChangeListener
@@ -230,37 +202,35 @@ public PeripheralAPI( IAPIEnvironment environment )
@Override
public void onPeripheralChanged( ComputerSide side, IPeripheral newPeripheral )
{
- synchronized( m_peripherals )
+ synchronized( peripherals )
{
int index = side.ordinal();
- if( m_peripherals[index] != null )
+ if( peripherals[index] != null )
{
// Queue a detachment
- final PeripheralWrapper wrapper = m_peripherals[index];
+ final PeripheralWrapper wrapper = peripherals[index];
if( wrapper.isAttached() ) wrapper.detach();
// Queue a detachment event
- m_environment.queueEvent( "peripheral_detach", new Object[] { side.getName() } );
+ environment.queueEvent( "peripheral_detach", side.getName() );
}
// Assign the new peripheral
- m_peripherals[index] = newPeripheral == null ? null
+ peripherals[index] = newPeripheral == null ? null
: new PeripheralWrapper( newPeripheral, side.getName() );
- if( m_peripherals[index] != null )
+ if( peripherals[index] != null )
{
// Queue an attachment
- final PeripheralWrapper wrapper = m_peripherals[index];
- if( m_running && !wrapper.isAttached() ) wrapper.attach();
+ final PeripheralWrapper wrapper = peripherals[index];
+ if( running && !wrapper.isAttached() ) wrapper.attach();
// Queue an attachment event
- m_environment.queueEvent( "peripheral", new Object[] { side.getName() } );
+ environment.queueEvent( "peripheral", side.getName() );
}
}
}
- // ILuaAPI implementation
-
@Override
public String[] getNames()
{
@@ -270,12 +240,12 @@ public String[] getNames()
@Override
public void startup()
{
- synchronized( m_peripherals )
+ synchronized( peripherals )
{
- m_running = true;
+ running = true;
for( int i = 0; i < 6; i++ )
{
- PeripheralWrapper wrapper = m_peripherals[i];
+ PeripheralWrapper wrapper = peripherals[i];
if( wrapper != null && !wrapper.isAttached() ) wrapper.attach();
}
}
@@ -284,12 +254,12 @@ public void startup()
@Override
public void shutdown()
{
- synchronized( m_peripherals )
+ synchronized( peripherals )
{
- m_running = false;
+ running = false;
for( int i = 0; i < 6; i++ )
{
- PeripheralWrapper wrapper = m_peripherals[i];
+ PeripheralWrapper wrapper = peripherals[i];
if( wrapper != null && wrapper.isAttached() )
{
wrapper.detach();
@@ -298,98 +268,95 @@ public void shutdown()
}
}
- @Nonnull
- @Override
- public String[] getMethodNames()
+ @LuaFunction
+ public final boolean isPresent( String sideName )
{
- return new String[] {
- "isPresent",
- "getType",
- "getMethods",
- "call",
- };
+ ComputerSide side = ComputerSide.valueOfInsensitive( sideName );
+ if( side != null )
+ {
+ synchronized( peripherals )
+ {
+ PeripheralWrapper p = peripherals[side.ordinal()];
+ if( p != null ) return true;
+ }
+ }
+ return false;
}
- @Override
- public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException, InterruptedException
+ @LuaFunction
+ public final Object[] getType( String sideName )
{
- switch( method )
+ ComputerSide side = ComputerSide.valueOfInsensitive( sideName );
+ if( side == null ) return null;
+
+ synchronized( peripherals )
{
- case 0:
- {
- // isPresent
- boolean present = false;
- ComputerSide side = ComputerSide.valueOfInsensitive( getString( args, 0 ) );
- if( side != null )
- {
- synchronized( m_peripherals )
- {
- PeripheralWrapper p = m_peripherals[side.ordinal()];
- if( p != null ) present = true;
- }
- }
- return new Object[] { present };
- }
- case 1:
- {
- // getType
- ComputerSide side = ComputerSide.valueOfInsensitive( getString( args, 0 ) );
- if( side != null )
- {
- synchronized( m_peripherals )
- {
- PeripheralWrapper p = m_peripherals[side.ordinal()];
- if( p != null ) return new Object[] { p.getType() };
- }
- }
- return null;
- }
- case 2:
- {
- // getMethods
- ComputerSide side = ComputerSide.valueOfInsensitive( getString( args, 0 ) );
- if( side != null )
- {
- synchronized( m_peripherals )
- {
- PeripheralWrapper p = m_peripherals[side.ordinal()];
- if( p != null ) return new Object[] { p.getMethods() };
- }
- }
+ PeripheralWrapper p = peripherals[side.ordinal()];
+ if( p != null ) return new Object[] { p.getType() };
+ }
+ return null;
+ }
- return null;
- }
- case 3:
- {
- // call
- ComputerSide side = ComputerSide.valueOfInsensitive( getString( args, 0 ) );
- String methodName = getString( args, 1 );
- Object[] methodArgs = Arrays.copyOfRange( args, 2, args.length );
+ @LuaFunction
+ public final Object[] getMethods( String sideName )
+ {
+ ComputerSide side = ComputerSide.valueOfInsensitive( sideName );
+ if( side == null ) return null;
- if( side == null ) throw new LuaException( "No peripheral attached" );
+ synchronized( peripherals )
+ {
+ PeripheralWrapper p = peripherals[side.ordinal()];
+ if( p != null ) return new Object[] { p.getMethods() };
+ }
+ return null;
+ }
- PeripheralWrapper p;
- synchronized( m_peripherals )
- {
- p = m_peripherals[side.ordinal()];
- }
- if( p == null ) throw new LuaException( "No peripheral attached" );
+ @LuaFunction
+ public final MethodResult call( ILuaContext context, IArguments args ) throws LuaException
+ {
+ ComputerSide side = ComputerSide.valueOfInsensitive( args.getString( 0 ) );
+ String methodName = args.getString( 1 );
+ IArguments methodArgs = args.drop( 2 );
- try
- {
- return p.call( context, methodName, methodArgs );
- }
- catch( LuaException e )
- {
- // We increase the error level by one in order to shift the error level to where peripheral.call was
- // invoked. It would be possible to do it in Lua code, but would add significantly more overhead.
- if( e.getLevel() > 0 ) throw new FastLuaException( e.getMessage(), e.getLevel() + 1 );
- throw e;
- }
- }
- default:
- return null;
+ if( side == null ) throw new LuaException( "No peripheral attached" );
+
+ PeripheralWrapper p;
+ synchronized( peripherals )
+ {
+ p = peripherals[side.ordinal()];
+ }
+ if( p == null ) throw new LuaException( "No peripheral attached" );
+
+ try
+ {
+ return p.call( context, methodName, methodArgs ).adjustError( 1 );
+ }
+ catch( LuaException e )
+ {
+ // We increase the error level by one in order to shift the error level to where peripheral.call was
+ // invoked. It would be possible to do it in Lua code, but would add significantly more overhead.
+ if( e.getLevel() > 0 ) throw new FastLuaException( e.getMessage(), e.getLevel() + 1 );
+ throw e;
}
}
+ public static Map getMethods( IPeripheral peripheral )
+ {
+ String[] dynamicMethods = peripheral instanceof IDynamicPeripheral
+ ? Objects.requireNonNull( ((IDynamicPeripheral) peripheral).getMethodNames(), "Peripheral methods cannot be null" )
+ : LuaMethod.EMPTY_METHODS;
+
+ List> methods = PeripheralMethod.GENERATOR.getMethods( peripheral.getClass() );
+
+ Map methodMap = new HashMap<>( methods.size() + dynamicMethods.length );
+ for( int i = 0; i < dynamicMethods.length; i++ )
+ {
+ methodMap.put( dynamicMethods[i], PeripheralMethod.DYNAMIC.get( i ) );
+ }
+ for( NamedMethod method : methods )
+ {
+ methodMap.put( method.getName(), method.getMethod() );
+ }
+ return methodMap;
+ }
}
diff --git a/src/main/java/dan200/computercraft/core/apis/RedstoneAPI.java b/src/main/java/dan200/computercraft/core/apis/RedstoneAPI.java
index fbb8feffa..ce70fc5d2 100644
--- a/src/main/java/dan200/computercraft/core/apis/RedstoneAPI.java
+++ b/src/main/java/dan200/computercraft/core/apis/RedstoneAPI.java
@@ -6,14 +6,10 @@
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.ILuaAPI;
-import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.computer.ComputerSide;
-import javax.annotation.Nonnull;
-
-import static dan200.computercraft.api.lua.ArgumentHelper.*;
-
public class RedstoneAPI implements ILuaAPI
{
private final IAPIEnvironment environment;
@@ -29,95 +25,71 @@ public String[] getNames()
return new String[] { "rs", "redstone" };
}
- @Nonnull
- @Override
- public String[] getMethodNames()
+ @LuaFunction
+ public final String[] getSides()
{
- return new String[] {
- "getSides",
- "setOutput",
- "getOutput",
- "getInput",
- "setBundledOutput",
- "getBundledOutput",
- "getBundledInput",
- "testBundledInput",
- "setAnalogOutput",
- "setAnalogueOutput",
- "getAnalogOutput",
- "getAnalogueOutput",
- "getAnalogInput",
- "getAnalogueInput",
- };
+ return ComputerSide.NAMES;
}
- @Override
- public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
+ @LuaFunction
+ public final void setOutput( ComputerSide side, boolean output )
{
- switch( method )
- {
- case 0: // getSides
- return new Object[] { ComputerSide.NAMES };
- case 1:
- {
- // setOutput
- ComputerSide side = parseSide( args );
- boolean output = getBoolean( args, 1 );
- environment.setOutput( side, output ? 15 : 0 );
- return null;
- }
- case 2: // getOutput
- return new Object[] { environment.getOutput( parseSide( args ) ) > 0 };
- case 3: // getInput
- return new Object[] { environment.getInput( parseSide( args ) ) > 0 };
- case 4:
- {
- // setBundledOutput
- ComputerSide side = parseSide( args );
- int output = getInt( args, 1 );
- environment.setBundledOutput( side, output );
- return null;
- }
- case 5: // getBundledOutput
- return new Object[] { environment.getBundledOutput( parseSide( args ) ) };
- case 6: // getBundledInput
- return new Object[] { environment.getBundledInput( parseSide( args ) ) };
- case 7:
- {
- // testBundledInput
- ComputerSide side = parseSide( args );
- int mask = getInt( args, 1 );
- int input = environment.getBundledInput( side );
- return new Object[] { (input & mask) == mask };
- }
- case 8:
- case 9:
- {
- // setAnalogOutput/setAnalogueOutput
- ComputerSide side = parseSide( args );
- int output = getInt( args, 1 );
- if( output < 0 || output > 15 )
- {
- throw new LuaException( "Expected number in range 0-15" );
- }
- environment.setOutput( side, output );
- return null;
- }
- case 10:
- case 11: // getAnalogOutput/getAnalogueOutput
- return new Object[] { environment.getOutput( parseSide( args ) ) };
- case 12:
- case 13: // getAnalogInput/getAnalogueInput
- return new Object[] { environment.getInput( parseSide( args ) ) };
- default:
- return null;
- }
+ environment.setOutput( side, output ? 15 : 0 );
}
- private static ComputerSide parseSide( Object[] args ) throws LuaException
+ @LuaFunction
+ public final boolean getOutput( ComputerSide side )
{
- ComputerSide side = ComputerSide.valueOfInsensitive( getString( args, 0 ) );
- if( side == null ) throw new LuaException( "Invalid side." );
- return side;
+ return environment.getOutput( side ) > 0;
+ }
+
+ @LuaFunction
+ public final boolean getInput( ComputerSide side )
+ {
+ return environment.getInput( side ) > 0;
+ }
+
+ @LuaFunction( { "setAnalogOutput", "setAnalogueOutput" } )
+ public final void setAnalogOutput( ComputerSide side, int output ) throws LuaException
+ {
+ if( output < 0 || output > 15 ) throw new LuaException( "Expected number in range 0-15" );
+ environment.setOutput( side, output );
+ }
+
+ @LuaFunction( { "getAnalogOutput", "getAnalogueOutput" } )
+ public final int getAnalogOutput( ComputerSide side )
+ {
+ return environment.getOutput( side );
+ }
+
+ @LuaFunction( { "getAnalogInput", "getAnalogueInput" } )
+ public final int getAnalogInput( ComputerSide side )
+ {
+ return environment.getInput( side );
+ }
+
+ @LuaFunction
+ public final void setBundledOutput( ComputerSide side, int output )
+ {
+ environment.setBundledOutput( side, output );
+ }
+
+ @LuaFunction
+ public final int getBundledOutput( ComputerSide side )
+ {
+ return environment.getBundledOutput( side );
+ }
+
+ @LuaFunction
+ public final int getBundledInput( ComputerSide side )
+ {
+ return environment.getBundledOutput( side );
+ }
+
+ @LuaFunction
+ public final boolean testBundledInput( ComputerSide side, int mask )
+ {
+ int input = environment.getBundledInput( side );
+ return (input & mask) == mask;
}
}
diff --git a/src/main/java/dan200/computercraft/core/apis/TableHelper.java b/src/main/java/dan200/computercraft/core/apis/TableHelper.java
index 4ceae1659..296bfb0a1 100644
--- a/src/main/java/dan200/computercraft/core/apis/TableHelper.java
+++ b/src/main/java/dan200/computercraft/core/apis/TableHelper.java
@@ -5,14 +5,14 @@
*/
package dan200.computercraft.core.apis;
-import dan200.computercraft.api.lua.ArgumentHelper;
import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.LuaValues;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Map;
-import static dan200.computercraft.api.lua.ArgumentHelper.getNumericType;
+import static dan200.computercraft.api.lua.LuaValues.getNumericType;
/**
* Various helpers for tables.
@@ -27,7 +27,7 @@ private TableHelper()
@Nonnull
public static LuaException badKey( @Nonnull String key, @Nonnull String expected, @Nullable Object actual )
{
- return badKey( key, expected, ArgumentHelper.getType( actual ) );
+ return badKey( key, expected, LuaValues.getType( actual ) );
}
@Nonnull
diff --git a/src/main/java/dan200/computercraft/core/apis/TermAPI.java b/src/main/java/dan200/computercraft/core/apis/TermAPI.java
index 6bb290f67..195085331 100644
--- a/src/main/java/dan200/computercraft/core/apis/TermAPI.java
+++ b/src/main/java/dan200/computercraft/core/apis/TermAPI.java
@@ -6,27 +6,23 @@
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.ILuaAPI;
-import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.computer.IComputerEnvironment;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.util.Colour;
-import dan200.computercraft.shared.util.Palette;
-import org.apache.commons.lang3.ArrayUtils;
import javax.annotation.Nonnull;
-import static dan200.computercraft.api.lua.ArgumentHelper.*;
-
-public class TermAPI implements ILuaAPI
+public class TermAPI extends TermMethods implements ILuaAPI
{
- private final Terminal m_terminal;
- private final IComputerEnvironment m_environment;
+ private final Terminal terminal;
+ private final IComputerEnvironment environment;
public TermAPI( IAPIEnvironment environment )
{
- m_terminal = environment.getTerminal();
- m_environment = environment.getComputerEnvironment();
+ terminal = environment.getTerminal();
+ this.environment = environment.getComputerEnvironment();
}
@Override
@@ -35,262 +31,29 @@ public String[] getNames()
return new String[] { "term" };
}
+ @LuaFunction( { "nativePaletteColour", "nativePaletteColor" } )
+ public final Object[] nativePaletteColour( int colourArg ) throws LuaException
+ {
+ int colour = 15 - parseColour( colourArg );
+ Colour c = Colour.fromInt( colour );
+
+ float[] rgb = c.getRGB();
+
+ Object[] rgbObj = new Object[rgb.length];
+ for( int i = 0; i < rgbObj.length; ++i ) rgbObj[i] = rgb[i];
+ return rgbObj;
+ }
+
@Nonnull
@Override
- public String[] getMethodNames()
+ public Terminal getTerminal()
{
- return new String[] {
- "write",
- "scroll",
- "setCursorPos",
- "setCursorBlink",
- "getCursorPos",
- "getSize",
- "clear",
- "clearLine",
- "setTextColour",
- "setTextColor",
- "setBackgroundColour",
- "setBackgroundColor",
- "isColour",
- "isColor",
- "getTextColour",
- "getTextColor",
- "getBackgroundColour",
- "getBackgroundColor",
- "blit",
- "setPaletteColour",
- "setPaletteColor",
- "getPaletteColour",
- "getPaletteColor",
- "nativePaletteColour",
- "nativePaletteColor",
- "getCursorBlink",
- };
- }
-
- public static int parseColour( Object[] args ) throws LuaException
- {
- int colour = getInt( args, 0 );
- if( colour <= 0 )
- {
- throw new LuaException( "Colour out of range" );
- }
- colour = getHighestBit( colour ) - 1;
- if( colour < 0 || colour > 15 )
- {
- throw new LuaException( "Colour out of range" );
- }
- return colour;
- }
-
- public static Object[] encodeColour( int colour ) throws LuaException
- {
- return new Object[] { 1 << colour };
- }
-
- public static void setColour( Terminal terminal, int colour, double r, double g, double b )
- {
- if( terminal.getPalette() != null )
- {
- terminal.getPalette().setColour( colour, r, g, b );
- terminal.setChanged();
- }
+ return terminal;
}
@Override
- public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
+ public boolean isColour()
{
- switch( method )
- {
- case 0:
- {
- // write
- String text = args.length > 0 && args[0] != null ? args[0].toString() : "";
- synchronized( m_terminal )
- {
- m_terminal.write( text );
- m_terminal.setCursorPos( m_terminal.getCursorX() + text.length(), m_terminal.getCursorY() );
- }
- return null;
- }
- case 1:
- {
- // scroll
- int y = getInt( args, 0 );
- synchronized( m_terminal )
- {
- m_terminal.scroll( y );
- }
- return null;
- }
- case 2:
- {
- // setCursorPos
- int x = getInt( args, 0 ) - 1;
- int y = getInt( args, 1 ) - 1;
- synchronized( m_terminal )
- {
- m_terminal.setCursorPos( x, y );
- }
- return null;
- }
- case 3:
- {
- // setCursorBlink
- boolean b = getBoolean( args, 0 );
- synchronized( m_terminal )
- {
- m_terminal.setCursorBlink( b );
- }
- return null;
- }
- case 4:
- {
- // getCursorPos
- int x, y;
- synchronized( m_terminal )
- {
- x = m_terminal.getCursorX();
- y = m_terminal.getCursorY();
- }
- return new Object[] { x + 1, y + 1 };
- }
- case 5:
- {
- // getSize
- int width, height;
- synchronized( m_terminal )
- {
- width = m_terminal.getWidth();
- height = m_terminal.getHeight();
- }
- return new Object[] { width, height };
- }
- case 6: // clear
- synchronized( m_terminal )
- {
- m_terminal.clear();
- }
- return null;
- case 7: // clearLine
- synchronized( m_terminal )
- {
- m_terminal.clearLine();
- }
- return null;
- case 8:
- case 9:
- {
- // setTextColour/setTextColor
- int colour = parseColour( args );
- synchronized( m_terminal )
- {
- m_terminal.setTextColour( colour );
- }
- return null;
- }
- case 10:
- case 11:
- {
- // setBackgroundColour/setBackgroundColor
- int colour = parseColour( args );
- synchronized( m_terminal )
- {
- m_terminal.setBackgroundColour( colour );
- }
- return null;
- }
- case 12:
- case 13: // isColour/isColor
- return new Object[] { m_environment.isColour() };
- case 14:
- case 15: // getTextColour/getTextColor
- return encodeColour( m_terminal.getTextColour() );
- case 16:
- case 17: // getBackgroundColour/getBackgroundColor
- return encodeColour( m_terminal.getBackgroundColour() );
- case 18:
- {
- // blit
- String text = getString( args, 0 );
- String textColour = getString( args, 1 );
- String backgroundColour = getString( args, 2 );
- if( textColour.length() != text.length() || backgroundColour.length() != text.length() )
- {
- throw new LuaException( "Arguments must be the same length" );
- }
-
- synchronized( m_terminal )
- {
- m_terminal.blit( text, textColour, backgroundColour );
- m_terminal.setCursorPos( m_terminal.getCursorX() + text.length(), m_terminal.getCursorY() );
- }
- return null;
- }
- case 19:
- case 20:
- {
- // setPaletteColour/setPaletteColor
- int colour = 15 - parseColour( args );
- if( args.length == 2 )
- {
- int hex = getInt( args, 1 );
- double[] rgb = Palette.decodeRGB8( hex );
- setColour( m_terminal, colour, rgb[0], rgb[1], rgb[2] );
- }
- else
- {
- double r = getFiniteDouble( args, 1 );
- double g = getFiniteDouble( args, 2 );
- double b = getFiniteDouble( args, 3 );
- setColour( m_terminal, colour, r, g, b );
- }
- return null;
- }
- case 21:
- case 22:
- {
- // getPaletteColour/getPaletteColor
- int colour = 15 - parseColour( args );
- synchronized( m_terminal )
- {
- if( m_terminal.getPalette() != null )
- {
- return ArrayUtils.toObject( m_terminal.getPalette().getColour( colour ) );
- }
- }
- return null;
- }
- case 23:
- case 24:
- {
- // nativePaletteColour/nativePaletteColor
- int colour = 15 - parseColour( args );
- Colour c = Colour.fromInt( colour );
-
- float[] rgb = c.getRGB();
-
- Object[] rgbObj = new Object[rgb.length];
- for( int i = 0; i < rgbObj.length; ++i ) rgbObj[i] = rgb[i];
- return rgbObj;
- }
- case 25:
- // getCursorBlink
- return new Object[] { m_terminal.getCursorBlink() };
- default:
- return null;
- }
- }
-
- private static int getHighestBit( int group )
- {
- int bit = 0;
- while( group > 0 )
- {
- group >>= 1;
- bit++;
- }
- return bit;
+ return environment.isColour();
}
}
diff --git a/src/main/java/dan200/computercraft/core/apis/TermMethods.java b/src/main/java/dan200/computercraft/core/apis/TermMethods.java
new file mode 100644
index 000000000..d7674ef3f
--- /dev/null
+++ b/src/main/java/dan200/computercraft/core/apis/TermMethods.java
@@ -0,0 +1,222 @@
+/*
+ * This file is part of ComputerCraft - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
+ * Send enquiries to dratcliffe@gmail.com
+ */
+
+package dan200.computercraft.core.apis;
+
+import dan200.computercraft.api.lua.IArguments;
+import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.LuaFunction;
+import dan200.computercraft.core.terminal.Terminal;
+import dan200.computercraft.shared.util.Palette;
+import dan200.computercraft.shared.util.StringUtil;
+import org.apache.commons.lang3.ArrayUtils;
+
+import javax.annotation.Nonnull;
+
+/**
+ * A base class for all objects which interact with a terminal. Namely the {@link TermAPI} and monitors.
+ */
+public abstract class TermMethods
+{
+ private static int getHighestBit( int group )
+ {
+ int bit = 0;
+ while( group > 0 )
+ {
+ group >>= 1;
+ bit++;
+ }
+ return bit;
+ }
+
+ @Nonnull
+ public abstract Terminal getTerminal() throws LuaException;
+
+ public abstract boolean isColour() throws LuaException;
+
+ @LuaFunction
+ public final void write( IArguments arguments ) throws LuaException
+ {
+ String text = StringUtil.toString( arguments.get( 0 ) );
+ Terminal terminal = getTerminal();
+ synchronized( terminal )
+ {
+ terminal.write( text );
+ terminal.setCursorPos( terminal.getCursorX() + text.length(), terminal.getCursorY() );
+ }
+ }
+
+ @LuaFunction
+ public final void scroll( int y ) throws LuaException
+ {
+ getTerminal().scroll( y );
+ }
+
+ @LuaFunction
+ public final Object[] getCursorPos() throws LuaException
+ {
+ Terminal terminal = getTerminal();
+ return new Object[] { terminal.getCursorX() + 1, terminal.getCursorY() + 1 };
+ }
+
+ @LuaFunction
+ public final void setCursorPos( int x, int y ) throws LuaException
+ {
+ Terminal terminal = getTerminal();
+ synchronized( terminal )
+ {
+ terminal.setCursorPos( x - 1, y - 1 );
+ }
+ }
+
+ @LuaFunction
+ public final boolean getCursorBlink() throws LuaException
+ {
+ return getTerminal().getCursorBlink();
+ }
+
+ @LuaFunction
+ public final void setCursorBlink( boolean blink ) throws LuaException
+ {
+ Terminal terminal = getTerminal();
+ synchronized( terminal )
+ {
+ terminal.setCursorBlink( blink );
+ }
+ }
+
+ @LuaFunction
+ public final Object[] getSize() throws LuaException
+ {
+ Terminal terminal = getTerminal();
+ return new Object[] { terminal.getWidth(), terminal.getHeight() };
+ }
+
+ @LuaFunction
+ public final void clear() throws LuaException
+ {
+ getTerminal().clear();
+ }
+
+ @LuaFunction
+ public final void clearLine() throws LuaException
+ {
+ getTerminal().clearLine();
+ }
+
+ @LuaFunction( { "getTextColour", "getTextColor" } )
+ public final int getTextColour() throws LuaException
+ {
+ return encodeColour( getTerminal().getTextColour() );
+ }
+
+ @LuaFunction( { "setTextColour", "setTextColor" } )
+ public final void setTextColour( int colourArg ) throws LuaException
+ {
+ int colour = parseColour( colourArg );
+ Terminal terminal = getTerminal();
+ synchronized( terminal )
+ {
+ terminal.setTextColour( colour );
+ }
+ }
+
+ @LuaFunction( { "getBackgroundColour", "getBackgroundColor" } )
+ public final int getBackgroundColour() throws LuaException
+ {
+ return encodeColour( getTerminal().getBackgroundColour() );
+ }
+
+ @LuaFunction( { "setBackgroundColour", "setBackgroundColor" } )
+ public final void setBackgroundColour( int colourArg ) throws LuaException
+ {
+ int colour = parseColour( colourArg );
+ Terminal terminal = getTerminal();
+ synchronized( terminal )
+ {
+ terminal.setBackgroundColour( colour );
+ }
+ }
+
+ @LuaFunction( { "isColour", "isColor" } )
+ public final boolean getIsColour() throws LuaException
+ {
+ return isColour();
+ }
+
+ @LuaFunction
+ public final void blit( String text, String textColour, String backgroundColour ) throws LuaException
+ {
+ if( textColour.length() != text.length() || backgroundColour.length() != text.length() )
+ {
+ throw new LuaException( "Arguments must be the same length" );
+ }
+
+ Terminal terminal = getTerminal();
+ synchronized( terminal )
+ {
+ terminal.blit( text, textColour, backgroundColour );
+ terminal.setCursorPos( terminal.getCursorX() + text.length(), terminal.getCursorY() );
+ }
+ }
+
+ @LuaFunction( { "setPaletteColour", "setPaletteColor" } )
+ public final void setPaletteColour( IArguments args ) throws LuaException
+ {
+ int colour = 15 - parseColour( args.getInt( 0 ) );
+ if( args.count() == 2 )
+ {
+ int hex = args.getInt( 1 );
+ double[] rgb = Palette.decodeRGB8( hex );
+ setColour( getTerminal(), colour, rgb[0], rgb[1], rgb[2] );
+ }
+ else
+ {
+ double r = args.getFiniteDouble( 1 );
+ double g = args.getFiniteDouble( 2 );
+ double b = args.getFiniteDouble( 3 );
+ setColour( getTerminal(), colour, r, g, b );
+ }
+ }
+
+ @LuaFunction( { "getPaletteColour", "getPaletteColor" } )
+ public final Object[] getPaletteColour( int colourArg ) throws LuaException
+ {
+ int colour = 15 - parseColour( colourArg );
+ Terminal terminal = getTerminal();
+ synchronized( terminal )
+ {
+ if( terminal.getPalette() != null )
+ {
+ return ArrayUtils.toObject( terminal.getPalette().getColour( colour ) );
+ }
+ }
+ return null;
+ }
+
+ public static int parseColour( int colour ) throws LuaException
+ {
+ if( colour <= 0 ) throw new LuaException( "Colour out of range" );
+ colour = getHighestBit( colour ) - 1;
+ if( colour < 0 || colour > 15 ) throw new LuaException( "Colour out of range" );
+ return colour;
+ }
+
+
+ public static int encodeColour( int colour )
+ {
+ return 1 << colour;
+ }
+
+ public static void setColour( Terminal terminal, int colour, double r, double g, double b )
+ {
+ if( terminal.getPalette() != null )
+ {
+ terminal.getPalette().setColour( colour, r, g, b );
+ terminal.setChanged();
+ }
+ }
+}
diff --git a/src/main/java/dan200/computercraft/core/apis/handles/BinaryReadableHandle.java b/src/main/java/dan200/computercraft/core/apis/handles/BinaryReadableHandle.java
index df0ff48d5..ce9fda477 100644
--- a/src/main/java/dan200/computercraft/core/apis/handles/BinaryReadableHandle.java
+++ b/src/main/java/dan200/computercraft/core/apis/handles/BinaryReadableHandle.java
@@ -5,11 +5,10 @@
*/
package dan200.computercraft.core.apis.handles;
-import com.google.common.collect.ObjectArrays;
-import dan200.computercraft.api.lua.ILuaContext;
+import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.LuaFunction;
-import javax.annotation.Nonnull;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
@@ -17,208 +16,205 @@
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
-
-import static dan200.computercraft.api.lua.ArgumentHelper.getInt;
-import static dan200.computercraft.api.lua.ArgumentHelper.optBoolean;
+import java.util.Optional;
public class BinaryReadableHandle extends HandleGeneric
{
private static final int BUFFER_SIZE = 8192;
- private static final String[] METHOD_NAMES = new String[] { "read", "readAll", "readLine", "close" };
- private static final String[] METHOD_SEEK_NAMES = ObjectArrays.concat( METHOD_NAMES, new String[] { "seek" }, String.class );
-
- private final ReadableByteChannel m_reader;
- private final SeekableByteChannel m_seekable;
+ private final ReadableByteChannel reader;
+ final SeekableByteChannel seekable;
private final ByteBuffer single = ByteBuffer.allocate( 1 );
- public BinaryReadableHandle( ReadableByteChannel channel, Closeable closeable )
+ protected BinaryReadableHandle( ReadableByteChannel reader, SeekableByteChannel seekable, Closeable closeable )
{
super( closeable );
- m_reader = channel;
- m_seekable = asSeekable( channel );
+ this.reader = reader;
+ this.seekable = seekable;
}
- public BinaryReadableHandle( ReadableByteChannel channel )
+ public static BinaryReadableHandle of( ReadableByteChannel channel, Closeable closeable )
{
- this( channel, channel );
+ SeekableByteChannel seekable = asSeekable( channel );
+ return seekable == null ? new BinaryReadableHandle( channel, null, closeable ) : new Seekable( seekable, closeable );
}
- @Nonnull
- @Override
- public String[] getMethodNames()
+ public static BinaryReadableHandle of( ReadableByteChannel channel )
{
- return m_seekable == null ? METHOD_NAMES : METHOD_SEEK_NAMES;
+ return of( channel, channel );
}
- @Override
- public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
+ @LuaFunction
+ public final Object[] read( Optional countArg ) throws LuaException
{
- switch( method )
+ checkOpen();
+ try
{
- case 0: // read
- checkOpen();
- try
- {
- if( args.length > 0 && args[0] != null )
- {
- int count = getInt( args, 0 );
- if( count < 0 )
- {
- throw new LuaException( "Cannot read a negative number of bytes" );
- }
- else if( count == 0 && m_seekable != null )
- {
- return m_seekable.position() >= m_seekable.size() ? null : new Object[] { "" };
- }
-
- if( count <= BUFFER_SIZE )
- {
- ByteBuffer buffer = ByteBuffer.allocate( count );
-
- int read = m_reader.read( buffer );
- if( read < 0 ) return null;
- return new Object[] { read < count ? Arrays.copyOf( buffer.array(), read ) : buffer.array() };
- }
- else
- {
- // Read the initial set of characters, failing if none are read.
- ByteBuffer buffer = ByteBuffer.allocate( BUFFER_SIZE );
- int read = m_reader.read( buffer );
- if( read < 0 ) return null;
-
- // If we failed to read "enough" here, let's just abort
- if( read >= count || read < BUFFER_SIZE )
- {
- return new Object[] { Arrays.copyOf( buffer.array(), read ) };
- }
-
- // Build up an array of ByteBuffers. Hopefully this means we can perform less allocation
- // than doubling up the buffer each time.
- int totalRead = read;
- List parts = new ArrayList<>( 4 );
- parts.add( buffer );
- while( read >= BUFFER_SIZE && totalRead < count )
- {
- buffer = ByteBuffer.allocate( Math.min( BUFFER_SIZE, count - totalRead ) );
- read = m_reader.read( buffer );
- if( read < 0 ) break;
-
- totalRead += read;
- parts.add( buffer );
- }
-
- // Now just copy all the bytes across!
- byte[] bytes = new byte[totalRead];
- int pos = 0;
- for( ByteBuffer part : parts )
- {
- System.arraycopy( part.array(), 0, bytes, pos, part.position() );
- pos += part.position();
- }
- return new Object[] { bytes };
- }
- }
- else
- {
- single.clear();
- int b = m_reader.read( single );
- return b == -1 ? null : new Object[] { single.get( 0 ) & 0xFF };
- }
- }
- catch( IOException e )
- {
- return null;
- }
- case 1: // readAll
- checkOpen();
- try
- {
- int expected = 32;
- if( m_seekable != null )
- {
- expected = Math.max( expected, (int) (m_seekable.size() - m_seekable.position()) );
- }
- ByteArrayOutputStream stream = new ByteArrayOutputStream( expected );
-
- ByteBuffer buf = ByteBuffer.allocate( 8192 );
- boolean readAnything = false;
- while( true )
- {
- buf.clear();
- int r = m_reader.read( buf );
- if( r == -1 ) break;
-
- readAnything = true;
- stream.write( buf.array(), 0, r );
- }
- return readAnything ? new Object[] { stream.toByteArray() } : null;
- }
- catch( IOException e )
- {
- return null;
- }
- case 2: // readLine
+ if( countArg.isPresent() )
{
- checkOpen();
- boolean withTrailing = optBoolean( args, 0, false );
- try
+ int count = countArg.get();
+ if( count < 0 ) throw new LuaException( "Cannot read a negative number of bytes" );
+ if( count == 0 && seekable != null )
{
- ByteArrayOutputStream stream = new ByteArrayOutputStream();
-
- boolean readAnything = false, readRc = false;
- while( true )
- {
- single.clear();
- int read = m_reader.read( single );
- if( read <= 0 )
- {
- // Nothing else to read, and we saw no \n. Return the array. If we saw a \r, then add it
- // back.
- if( readRc ) stream.write( '\r' );
- return readAnything ? new Object[] { stream.toByteArray() } : null;
- }
-
- readAnything = true;
-
- byte chr = single.get( 0 );
- if( chr == '\n' )
- {
- if( withTrailing )
- {
- if( readRc ) stream.write( '\r' );
- stream.write( chr );
- }
- return new Object[] { stream.toByteArray() };
- }
- else
- {
- // We want to skip \r\n, but obviously need to include cases where \r is not followed by \n.
- // Note, this behaviour is non-standard compliant (strictly speaking we should have no
- // special logic for \r), but we preserve compatibility with EncodedReadableHandle and
- // previous behaviour of the io library.
- if( readRc ) stream.write( '\r' );
- readRc = chr == '\r';
- if( !readRc ) stream.write( chr );
- }
- }
+ return seekable.position() >= seekable.size() ? null : new Object[] { "" };
}
- catch( IOException e )
+
+ if( count <= BUFFER_SIZE )
{
- return null;
+ ByteBuffer buffer = ByteBuffer.allocate( count );
+
+ int read = reader.read( buffer );
+ if( read < 0 ) return null;
+ buffer.flip();
+ return new Object[] { buffer };
+ }
+ else
+ {
+ // Read the initial set of characters, failing if none are read.
+ ByteBuffer buffer = ByteBuffer.allocate( BUFFER_SIZE );
+ int read = reader.read( buffer );
+ if( read < 0 ) return null;
+
+ // If we failed to read "enough" here, let's just abort
+ if( read >= count || read < BUFFER_SIZE )
+ {
+ buffer.flip();
+ return new Object[] { buffer };
+ }
+
+ // Build up an array of ByteBuffers. Hopefully this means we can perform less allocation
+ // than doubling up the buffer each time.
+ int totalRead = read;
+ List parts = new ArrayList<>( 4 );
+ parts.add( buffer );
+ while( read >= BUFFER_SIZE && totalRead < count )
+ {
+ buffer = ByteBuffer.allocate( Math.min( BUFFER_SIZE, count - totalRead ) );
+ read = reader.read( buffer );
+ if( read < 0 ) break;
+
+ totalRead += read;
+ parts.add( buffer );
+ }
+
+ // Now just copy all the bytes across!
+ byte[] bytes = new byte[totalRead];
+ int pos = 0;
+ for( ByteBuffer part : parts )
+ {
+ System.arraycopy( part.array(), 0, bytes, pos, part.position() );
+ pos += part.position();
+ }
+ return new Object[] { bytes };
}
}
- case 3: // close
- checkOpen();
- close();
- return null;
- case 4: // seek
- checkOpen();
- return handleSeek( m_seekable, args );
- default:
- return null;
+ else
+ {
+ single.clear();
+ int b = reader.read( single );
+ return b == -1 ? null : new Object[] { single.get( 0 ) & 0xFF };
+ }
+ }
+ catch( IOException e )
+ {
+ return null;
+ }
+ }
+
+ @LuaFunction
+ public final Object[] readAll() throws LuaException
+ {
+ checkOpen();
+ try
+ {
+ int expected = 32;
+ if( seekable != null ) expected = Math.max( expected, (int) (seekable.size() - seekable.position()) );
+ ByteArrayOutputStream stream = new ByteArrayOutputStream( expected );
+
+ ByteBuffer buf = ByteBuffer.allocate( 8192 );
+ boolean readAnything = false;
+ while( true )
+ {
+ buf.clear();
+ int r = reader.read( buf );
+ if( r == -1 ) break;
+
+ readAnything = true;
+ stream.write( buf.array(), 0, r );
+ }
+ return readAnything ? new Object[] { stream.toByteArray() } : null;
+ }
+ catch( IOException e )
+ {
+ return null;
+ }
+ }
+
+ @LuaFunction
+ public final Object[] readLine( Optional withTrailingArg ) throws LuaException
+ {
+ checkOpen();
+ boolean withTrailing = withTrailingArg.orElse( false );
+ try
+ {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+
+ boolean readAnything = false, readRc = false;
+ while( true )
+ {
+ single.clear();
+ int read = reader.read( single );
+ if( read <= 0 )
+ {
+ // Nothing else to read, and we saw no \n. Return the array. If we saw a \r, then add it
+ // back.
+ if( readRc ) stream.write( '\r' );
+ return readAnything ? new Object[] { stream.toByteArray() } : null;
+ }
+
+ readAnything = true;
+
+ byte chr = single.get( 0 );
+ if( chr == '\n' )
+ {
+ if( withTrailing )
+ {
+ if( readRc ) stream.write( '\r' );
+ stream.write( chr );
+ }
+ return new Object[] { stream.toByteArray() };
+ }
+ else
+ {
+ // We want to skip \r\n, but obviously need to include cases where \r is not followed by \n.
+ // Note, this behaviour is non-standard compliant (strictly speaking we should have no
+ // special logic for \r), but we preserve compatibility with EncodedReadableHandle and
+ // previous behaviour of the io library.
+ if( readRc ) stream.write( '\r' );
+ readRc = chr == '\r';
+ if( !readRc ) stream.write( chr );
+ }
+ }
+ }
+ catch( IOException e )
+ {
+ return null;
+ }
+ }
+
+ public static class Seekable extends BinaryReadableHandle
+ {
+ public Seekable( SeekableByteChannel seekable, Closeable closeable )
+ {
+ super( seekable, seekable, closeable );
+ }
+
+ @LuaFunction
+ public final Object[] seek( IArguments arguments ) throws LuaException
+ {
+ checkOpen();
+ return handleSeek( seekable, arguments );
}
}
}
diff --git a/src/main/java/dan200/computercraft/core/apis/handles/BinaryWritableHandle.java b/src/main/java/dan200/computercraft/core/apis/handles/BinaryWritableHandle.java
index 2ab94bd4b..b5b3647bb 100644
--- a/src/main/java/dan200/computercraft/core/apis/handles/BinaryWritableHandle.java
+++ b/src/main/java/dan200/computercraft/core/apis/handles/BinaryWritableHandle.java
@@ -5,13 +5,11 @@
*/
package dan200.computercraft.core.apis.handles;
-import com.google.common.collect.ObjectArrays;
-import dan200.computercraft.api.lua.ArgumentHelper;
-import dan200.computercraft.api.lua.ILuaContext;
+import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.LuaException;
-import dan200.computercraft.shared.util.StringUtil;
+import dan200.computercraft.api.lua.LuaFunction;
+import dan200.computercraft.api.lua.LuaValues;
-import javax.annotation.Nonnull;
import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -21,87 +19,85 @@
public class BinaryWritableHandle extends HandleGeneric
{
- private static final String[] METHOD_NAMES = new String[] { "write", "flush", "close" };
- private static final String[] METHOD_SEEK_NAMES = ObjectArrays.concat( METHOD_NAMES, new String[] { "seek" }, String.class );
-
- private final WritableByteChannel m_writer;
- private final SeekableByteChannel m_seekable;
+ private final WritableByteChannel writer;
+ final SeekableByteChannel seekable;
private final ByteBuffer single = ByteBuffer.allocate( 1 );
- public BinaryWritableHandle( WritableByteChannel channel, Closeable closeable )
+ protected BinaryWritableHandle( WritableByteChannel writer, SeekableByteChannel seekable, Closeable closeable )
{
super( closeable );
- m_writer = channel;
- m_seekable = asSeekable( channel );
+ this.writer = writer;
+ this.seekable = seekable;
}
- public BinaryWritableHandle( WritableByteChannel channel )
+ public static BinaryWritableHandle of( WritableByteChannel channel, Closeable closeable )
{
- this( channel, channel );
+ SeekableByteChannel seekable = asSeekable( channel );
+ return seekable == null ? new BinaryWritableHandle( channel, null, closeable ) : new Seekable( seekable, closeable );
}
- @Nonnull
- @Override
- public String[] getMethodNames()
+ public static BinaryWritableHandle of( WritableByteChannel channel )
{
- return m_seekable == null ? METHOD_NAMES : METHOD_SEEK_NAMES;
+ return of( channel, channel );
}
- @Override
- public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
+ @LuaFunction
+ public final void write( IArguments arguments ) throws LuaException
{
- switch( method )
+ checkOpen();
+ try
{
- case 0: // write
- checkOpen();
- try
- {
- if( args.length > 0 && args[0] instanceof Number )
- {
- int number = ((Number) args[0]).intValue();
- single.clear();
- single.put( (byte) number );
- single.flip();
+ Object arg = arguments.get( 0 );
+ if( arg instanceof Number )
+ {
+ int number = ((Number) arg).intValue();
+ single.clear();
+ single.put( (byte) number );
+ single.flip();
- m_writer.write( single );
- }
- else if( args.length > 0 && args[0] instanceof String )
- {
- String value = (String) args[0];
- m_writer.write( ByteBuffer.wrap( StringUtil.encodeString( value ) ) );
- }
- else
- {
- throw ArgumentHelper.badArgumentOf( 0, "string or number", args.length > 0 ? args[0] : null );
- }
- return null;
- }
- catch( IOException e )
- {
- throw new LuaException( e.getMessage() );
- }
- case 1: // flush
- checkOpen();
- try
- {
- // Technically this is not needed
- if( m_writer instanceof FileChannel ) ((FileChannel) m_writer).force( false );
+ writer.write( single );
+ }
+ else if( arg instanceof String )
+ {
+ writer.write( arguments.getBytes( 0 ) );
+ }
+ else
+ {
+ throw LuaValues.badArgumentOf( 0, "string or number", arg );
+ }
+ }
+ catch( IOException e )
+ {
+ throw new LuaException( e.getMessage() );
+ }
+ }
- return null;
- }
- catch( IOException e )
- {
- return null;
- }
- case 2: // close
- checkOpen();
- close();
- return null;
- case 3: // seek
- checkOpen();
- return handleSeek( m_seekable, args );
- default:
- return null;
+ @LuaFunction
+ public final void flush() throws LuaException
+ {
+ checkOpen();
+ try
+ {
+ // Technically this is not needed
+ if( writer instanceof FileChannel ) ((FileChannel) writer).force( false );
+ }
+ catch( IOException ignored )
+ {
+ }
+ }
+
+ public static class Seekable extends BinaryWritableHandle
+ {
+ public Seekable( SeekableByteChannel seekable, Closeable closeable )
+ {
+ super( seekable, seekable, closeable );
+ }
+
+ @LuaFunction
+ public final Object[] seek( IArguments args ) throws LuaException
+ {
+ checkOpen();
+ return handleSeek( seekable, args );
}
}
}
diff --git a/src/main/java/dan200/computercraft/core/apis/handles/EncodedReadableHandle.java b/src/main/java/dan200/computercraft/core/apis/handles/EncodedReadableHandle.java
index af714dbdc..0a935fc22 100644
--- a/src/main/java/dan200/computercraft/core/apis/handles/EncodedReadableHandle.java
+++ b/src/main/java/dan200/computercraft/core/apis/handles/EncodedReadableHandle.java
@@ -5,8 +5,8 @@
*/
package dan200.computercraft.core.apis.handles;
-import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.LuaFunction;
import javax.annotation.Nonnull;
import java.io.BufferedReader;
@@ -18,20 +18,18 @@
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
-
-import static dan200.computercraft.api.lua.ArgumentHelper.optBoolean;
-import static dan200.computercraft.api.lua.ArgumentHelper.optInt;
+import java.util.Optional;
public class EncodedReadableHandle extends HandleGeneric
{
private static final int BUFFER_SIZE = 8192;
- private BufferedReader m_reader;
+ private final BufferedReader reader;
public EncodedReadableHandle( @Nonnull BufferedReader reader, @Nonnull Closeable closable )
{
super( closable );
- m_reader = reader;
+ this.reader = reader;
}
public EncodedReadableHandle( @Nonnull BufferedReader reader )
@@ -39,123 +37,107 @@ public EncodedReadableHandle( @Nonnull BufferedReader reader )
this( reader, reader );
}
- @Nonnull
- @Override
- public String[] getMethodNames()
+ @LuaFunction
+ public final Object[] readLine( Optional withTrailingArg ) throws LuaException
{
- return new String[] {
- "readLine",
- "readAll",
- "read",
- "close",
- };
+ checkOpen();
+ boolean withTrailing = withTrailingArg.orElse( false );
+ try
+ {
+ String line = reader.readLine();
+ if( line != null )
+ {
+ // While this is technically inaccurate, it's better than nothing
+ if( withTrailing ) line += "\n";
+ return new Object[] { line };
+ }
+ else
+ {
+ return null;
+ }
+ }
+ catch( IOException e )
+ {
+ return null;
+ }
}
- @Override
- public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
+ @LuaFunction
+ public final Object[] readAll() throws LuaException
{
- switch( method )
+ checkOpen();
+ try
{
- case 0: // readLine
+ StringBuilder result = new StringBuilder();
+ String line = reader.readLine();
+ while( line != null )
{
- checkOpen();
- boolean withTrailing = optBoolean( args, 0, false );
- try
+ result.append( line );
+ line = reader.readLine();
+ if( line != null )
{
- String line = m_reader.readLine();
- if( line != null )
- {
- // While this is technically inaccurate, it's better than nothing
- if( withTrailing ) line += "\n";
- return new Object[] { line };
- }
- else
- {
- return null;
- }
- }
- catch( IOException e )
- {
- return null;
+ result.append( "\n" );
}
}
- case 1: // readAll
- checkOpen();
- try
+ return new Object[] { result.toString() };
+ }
+ catch( IOException e )
+ {
+ return null;
+ }
+ }
+
+ @LuaFunction
+ public final Object[] read( Optional countA ) throws LuaException
+ {
+ checkOpen();
+ try
+ {
+ int count = countA.orElse( 1 );
+ if( count < 0 )
+ {
+ // Whilst this may seem absurd to allow reading 0 characters, PUC Lua it so
+ // it seems best to remain somewhat consistent.
+ throw new LuaException( "Cannot read a negative number of characters" );
+ }
+ else if( count <= BUFFER_SIZE )
+ {
+ // If we've got a small count, then allocate that and read it.
+ char[] chars = new char[count];
+ int read = reader.read( chars );
+
+ return read < 0 ? null : new Object[] { new String( chars, 0, read ) };
+ }
+ else
+ {
+ // If we've got a large count, read in bunches of 8192.
+ char[] buffer = new char[BUFFER_SIZE];
+
+ // Read the initial set of characters, failing if none are read.
+ int read = reader.read( buffer, 0, Math.min( buffer.length, count ) );
+ if( read < 0 ) return null;
+
+ StringBuilder out = new StringBuilder( read );
+ int totalRead = read;
+ out.append( buffer, 0, read );
+
+ // Otherwise read until we either reach the limit or we no longer consume
+ // the full buffer.
+ while( read >= BUFFER_SIZE && totalRead < count )
{
- StringBuilder result = new StringBuilder();
- String line = m_reader.readLine();
- while( line != null )
- {
- result.append( line );
- line = m_reader.readLine();
- if( line != null )
- {
- result.append( "\n" );
- }
- }
- return new Object[] { result.toString() };
+ read = reader.read( buffer, 0, Math.min( BUFFER_SIZE, count - totalRead ) );
+ if( read < 0 ) break;
+
+ totalRead += read;
+ out.append( buffer, 0, read );
}
- catch( IOException e )
- {
- return null;
- }
- case 2: // read
- checkOpen();
- try
- {
- int count = optInt( args, 0, 1 );
- if( count < 0 )
- {
- // Whilst this may seem absurd to allow reading 0 characters, PUC Lua it so
- // it seems best to remain somewhat consistent.
- throw new LuaException( "Cannot read a negative number of characters" );
- }
- else if( count <= BUFFER_SIZE )
- {
- // If we've got a small count, then allocate that and read it.
- char[] chars = new char[count];
- int read = m_reader.read( chars );
- return read < 0 ? null : new Object[] { new String( chars, 0, read ) };
- }
- else
- {
- // If we've got a large count, read in bunches of 8192.
- char[] buffer = new char[BUFFER_SIZE];
-
- // Read the initial set of characters, failing if none are read.
- int read = m_reader.read( buffer, 0, Math.min( buffer.length, count ) );
- if( read < 0 ) return null;
-
- StringBuilder out = new StringBuilder( read );
- int totalRead = read;
- out.append( buffer, 0, read );
-
- // Otherwise read until we either reach the limit or we no longer consume
- // the full buffer.
- while( read >= BUFFER_SIZE && totalRead < count )
- {
- read = m_reader.read( buffer, 0, Math.min( BUFFER_SIZE, count - totalRead ) );
- if( read < 0 ) break;
-
- totalRead += read;
- out.append( buffer, 0, read );
- }
-
- return new Object[] { out.toString() };
- }
- }
- catch( IOException e )
- {
- return null;
- }
- case 3: // close
- checkOpen();
- close();
- return null;
- default:
- return null;
+ return new Object[] { out.toString() };
+ }
+ }
+ catch( IOException e )
+ {
+ return null;
}
}
diff --git a/src/main/java/dan200/computercraft/core/apis/handles/EncodedWritableHandle.java b/src/main/java/dan200/computercraft/core/apis/handles/EncodedWritableHandle.java
index c9cfb5ba7..61bfe152b 100644
--- a/src/main/java/dan200/computercraft/core/apis/handles/EncodedWritableHandle.java
+++ b/src/main/java/dan200/computercraft/core/apis/handles/EncodedWritableHandle.java
@@ -5,8 +5,10 @@
*/
package dan200.computercraft.core.apis.handles;
-import dan200.computercraft.api.lua.ILuaContext;
+import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.LuaFunction;
+import dan200.computercraft.shared.util.StringUtil;
import javax.annotation.Nonnull;
import java.io.BufferedWriter;
@@ -21,85 +23,57 @@
public class EncodedWritableHandle extends HandleGeneric
{
- private BufferedWriter m_writer;
+ private final BufferedWriter writer;
public EncodedWritableHandle( @Nonnull BufferedWriter writer, @Nonnull Closeable closable )
{
super( closable );
- m_writer = writer;
+ this.writer = writer;
}
- public EncodedWritableHandle( @Nonnull BufferedWriter writer )
+ @LuaFunction
+ public final void write( IArguments args ) throws LuaException
{
- this( writer, writer );
- }
-
- @Nonnull
- @Override
- public String[] getMethodNames()
- {
- return new String[] {
- "write",
- "writeLine",
- "flush",
- "close",
- };
- }
-
- @Override
- public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
- {
- switch( method )
+ checkOpen();
+ String text = StringUtil.toString( args.get( 0 ) );
+ try
{
- case 0: // write
- {
- checkOpen();
- String text = args.length > 0 && args[0] != null ? args[0].toString() : "";
- try
- {
- m_writer.write( text, 0, text.length() );
- return null;
- }
- catch( IOException e )
- {
- throw new LuaException( e.getMessage() );
- }
- }
- case 1: // writeLine
- {
- checkOpen();
- String text = args.length > 0 && args[0] != null ? args[0].toString() : "";
- try
- {
- m_writer.write( text, 0, text.length() );
- m_writer.newLine();
- return null;
- }
- catch( IOException e )
- {
- throw new LuaException( e.getMessage() );
- }
- }
- case 2: // flush
- checkOpen();
- try
- {
- m_writer.flush();
- return null;
- }
- catch( IOException e )
- {
- return null;
- }
- case 3: // close
- checkOpen();
- close();
- return null;
- default:
- return null;
+ writer.write( text, 0, text.length() );
+ }
+ catch( IOException e )
+ {
+ throw new LuaException( e.getMessage() );
}
}
+ @LuaFunction
+ public final void writeLine( IArguments args ) throws LuaException
+ {
+ checkOpen();
+ String text = StringUtil.toString( args.get( 0 ) );
+ try
+ {
+ writer.write( text, 0, text.length() );
+ writer.newLine();
+ }
+ catch( IOException e )
+ {
+ throw new LuaException( e.getMessage() );
+ }
+ }
+
+ @LuaFunction
+ public final void flush() throws LuaException
+ {
+ checkOpen();
+ try
+ {
+ writer.flush();
+ }
+ catch( IOException ignored )
+ {
+ }
+ }
public static BufferedWriter openUtf8( WritableByteChannel channel )
{
diff --git a/src/main/java/dan200/computercraft/core/apis/handles/HandleGeneric.java b/src/main/java/dan200/computercraft/core/apis/handles/HandleGeneric.java
index 2ebad4202..35b5fd883 100644
--- a/src/main/java/dan200/computercraft/core/apis/handles/HandleGeneric.java
+++ b/src/main/java/dan200/computercraft/core/apis/handles/HandleGeneric.java
@@ -5,8 +5,9 @@
*/
package dan200.computercraft.core.apis.handles;
-import dan200.computercraft.api.lua.ILuaObject;
+import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.shared.util.IoUtil;
import javax.annotation.Nonnull;
@@ -15,36 +16,41 @@
import java.nio.channels.Channel;
import java.nio.channels.SeekableByteChannel;
-import static dan200.computercraft.api.lua.ArgumentHelper.optLong;
-import static dan200.computercraft.api.lua.ArgumentHelper.optString;
-
-public abstract class HandleGeneric implements ILuaObject
+public abstract class HandleGeneric
{
- private Closeable m_closable;
- private boolean m_open = true;
+ private Closeable closable;
+ private boolean open = true;
protected HandleGeneric( @Nonnull Closeable closable )
{
- m_closable = closable;
+ this.closable = closable;
}
protected void checkOpen() throws LuaException
{
- if( !m_open ) throw new LuaException( "attempt to use a closed file" );
+ if( !open ) throw new LuaException( "attempt to use a closed file" );
}
protected final void close()
{
- m_open = false;
+ open = false;
- Closeable closeable = m_closable;
+ Closeable closeable = closable;
if( closeable != null )
{
IoUtil.closeQuietly( closeable );
- m_closable = null;
+ closable = null;
}
}
+ @LuaFunction( "close" )
+ public final void doClose() throws LuaException
+ {
+ checkOpen();
+ close();
+ }
+
+
/**
* Shared implementation for various file handle types.
*
@@ -54,12 +60,12 @@ protected final void close()
* @throws LuaException If the arguments were invalid
* @see {@code file:seek} in the Lua manual.
*/
- protected static Object[] handleSeek( SeekableByteChannel channel, Object[] args ) throws LuaException
+ protected static Object[] handleSeek( SeekableByteChannel channel, IArguments args ) throws LuaException
{
+ String whence = args.optString( 0, "cur" );
+ long offset = args.optLong( 1, 0 );
try
{
- String whence = optString( args, 0, "cur" );
- long offset = optLong( args, 1, 0 );
switch( whence )
{
case "set":
diff --git a/src/main/java/dan200/computercraft/core/apis/http/CheckUrl.java b/src/main/java/dan200/computercraft/core/apis/http/CheckUrl.java
index ab4b56de2..f92d5926e 100644
--- a/src/main/java/dan200/computercraft/core/apis/http/CheckUrl.java
+++ b/src/main/java/dan200/computercraft/core/apis/http/CheckUrl.java
@@ -47,11 +47,11 @@ private void doRun()
try
{
NetworkUtils.getAddress( host, 80, false );
- if( tryClose() ) environment.queueEvent( EVENT, new Object[] { address, true } );
+ if( tryClose() ) environment.queueEvent( EVENT, address, true );
}
catch( HTTPRequestException e )
{
- if( tryClose() ) environment.queueEvent( EVENT, new Object[] { address, false, e.getMessage() } );
+ if( tryClose() ) environment.queueEvent( EVENT, address, false, e.getMessage() );
}
}
diff --git a/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequest.java b/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequest.java
index b15ce77d5..3af3bacc3 100644
--- a/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequest.java
+++ b/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequest.java
@@ -6,7 +6,6 @@
package dan200.computercraft.core.apis.http.request;
import dan200.computercraft.ComputerCraft;
-import dan200.computercraft.api.lua.ILuaObject;
import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.apis.http.HTTPRequestException;
import dan200.computercraft.core.apis.http.NetworkUtils;
@@ -201,7 +200,7 @@ protected void initChannel( SocketChannel ch )
void failure( String message )
{
- if( tryClose() ) environment.queueEvent( FAILURE_EVENT, new Object[] { address, message } );
+ if( tryClose() ) environment.queueEvent( FAILURE_EVENT, address, message );
}
void failure( Throwable cause )
@@ -227,14 +226,14 @@ else if( cause instanceof ReadTimeoutException || cause instanceof ConnectTimeou
failure( message );
}
- void failure( String message, ILuaObject object )
+ void failure( String message, HttpResponseHandle object )
{
- if( tryClose() ) environment.queueEvent( FAILURE_EVENT, new Object[] { address, message, object } );
+ if( tryClose() ) environment.queueEvent( FAILURE_EVENT, address, message, object );
}
- void success( ILuaObject object )
+ void success( HttpResponseHandle object )
{
- if( tryClose() ) environment.queueEvent( SUCCESS_EVENT, new Object[] { address, object } );
+ if( tryClose() ) environment.queueEvent( SUCCESS_EVENT, address, object );
}
@Override
diff --git a/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequestHandler.java b/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequestHandler.java
index 866d0d138..d742ec538 100644
--- a/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequestHandler.java
+++ b/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequestHandler.java
@@ -6,10 +6,10 @@
package dan200.computercraft.core.apis.http.request;
import dan200.computercraft.ComputerCraft;
-import dan200.computercraft.api.lua.ILuaObject;
import dan200.computercraft.core.apis.handles.ArrayByteChannel;
import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
import dan200.computercraft.core.apis.handles.EncodedReadableHandle;
+import dan200.computercraft.core.apis.handles.HandleGeneric;
import dan200.computercraft.core.apis.http.HTTPRequestException;
import dan200.computercraft.core.apis.http.NetworkUtils;
import dan200.computercraft.core.tracking.TrackingField;
@@ -209,10 +209,10 @@ private void sendResponse()
// Prepare to queue an event
ArrayByteChannel contents = new ArrayByteChannel( bytes );
- final ILuaObject reader = request.isBinary()
- ? new BinaryReadableHandle( contents )
+ HandleGeneric reader = request.isBinary()
+ ? BinaryReadableHandle.of( contents )
: new EncodedReadableHandle( EncodedReadableHandle.open( contents, responseCharset ) );
- ILuaObject stream = new HttpResponseHandle( reader, status.code(), status.reasonPhrase(), headers );
+ HttpResponseHandle stream = new HttpResponseHandle( reader, status.code(), status.reasonPhrase(), headers );
if( status.code() >= 200 && status.code() < 400 )
{
diff --git a/src/main/java/dan200/computercraft/core/apis/http/request/HttpResponseHandle.java b/src/main/java/dan200/computercraft/core/apis/http/request/HttpResponseHandle.java
index bfface4eb..8000a6c1e 100644
--- a/src/main/java/dan200/computercraft/core/apis/http/request/HttpResponseHandle.java
+++ b/src/main/java/dan200/computercraft/core/apis/http/request/HttpResponseHandle.java
@@ -5,62 +5,48 @@
*/
package dan200.computercraft.core.apis.http.request;
-import dan200.computercraft.api.lua.ILuaContext;
-import dan200.computercraft.api.lua.ILuaObject;
-import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.LuaFunction;
+import dan200.computercraft.core.apis.handles.HandleGeneric;
+import dan200.computercraft.core.asm.ObjectSource;
import javax.annotation.Nonnull;
-import java.util.Arrays;
+import java.util.Collections;
import java.util.Map;
/**
* Wraps a {@link dan200.computercraft.core.apis.handles.HandleGeneric} and provides additional methods for
* getting the response code and headers.
*/
-public class HttpResponseHandle implements ILuaObject
+public class HttpResponseHandle implements ObjectSource
{
- private final String[] newMethods;
- private final int methodOffset;
- private final ILuaObject reader;
+ private final Object reader;
private final int responseCode;
private final String responseStatus;
private final Map responseHeaders;
- public HttpResponseHandle( @Nonnull ILuaObject reader, int responseCode, String responseStatus, @Nonnull Map responseHeaders )
+ public HttpResponseHandle( @Nonnull HandleGeneric reader, int responseCode, String responseStatus, @Nonnull Map responseHeaders )
{
this.reader = reader;
this.responseCode = responseCode;
this.responseStatus = responseStatus;
this.responseHeaders = responseHeaders;
-
- String[] oldMethods = reader.getMethodNames();
- final int methodOffset = this.methodOffset = oldMethods.length;
-
- final String[] newMethods = this.newMethods = Arrays.copyOf( oldMethods, oldMethods.length + 2 );
- newMethods[methodOffset + 0] = "getResponseCode";
- newMethods[methodOffset + 1] = "getResponseHeaders";
}
- @Nonnull
- @Override
- public String[] getMethodNames()
+ @LuaFunction
+ public final Object[] getResponseCode()
{
- return newMethods;
+ return new Object[] { responseCode, responseStatus };
+ }
+
+ @LuaFunction
+ public final Map getResponseHeaders()
+ {
+ return responseHeaders;
}
@Override
- public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException, InterruptedException
+ public Iterable getExtra()
{
- if( method < methodOffset ) return reader.callMethod( context, method, args );
-
- switch( method - methodOffset )
- {
- case 0: // getResponseCode
- return new Object[] { responseCode, responseStatus };
- case 1: // getResponseHeaders
- return new Object[] { responseHeaders };
- default:
- return null;
- }
+ return Collections.singletonList( reader );
}
}
diff --git a/src/main/java/dan200/computercraft/core/apis/http/websocket/Websocket.java b/src/main/java/dan200/computercraft/core/apis/http/websocket/Websocket.java
index 5e1562d04..fe591f592 100644
--- a/src/main/java/dan200/computercraft/core/apis/http/websocket/Websocket.java
+++ b/src/main/java/dan200/computercraft/core/apis/http/websocket/Websocket.java
@@ -187,7 +187,7 @@ void success( Channel channel )
if( isClosed() ) return;
WebsocketHandle handle = new WebsocketHandle( this, channel );
- environment().queueEvent( SUCCESS_EVENT, new Object[] { address, handle } );
+ environment().queueEvent( SUCCESS_EVENT, address, handle );
websocketHandle = createOwnerReference( handle );
checkClosed();
@@ -195,18 +195,16 @@ void success( Channel channel )
void failure( String message )
{
- if( tryClose() ) environment.queueEvent( FAILURE_EVENT, new Object[] { address, message } );
+ if( tryClose() ) environment.queueEvent( FAILURE_EVENT, address, message );
}
void close( int status, String reason )
{
if( tryClose() )
{
- environment.queueEvent( CLOSE_EVENT, new Object[] {
- address,
+ environment.queueEvent( CLOSE_EVENT, address,
Strings.isNullOrEmpty( reason ) ? null : reason,
- status < 0 ? null : status,
- } );
+ status < 0 ? null : status );
}
}
diff --git a/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketHandle.java b/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketHandle.java
index 8433d30c3..78bb17bdf 100644
--- a/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketHandle.java
+++ b/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketHandle.java
@@ -7,10 +7,7 @@
import com.google.common.base.Objects;
import dan200.computercraft.ComputerCraft;
-import dan200.computercraft.api.lua.ArgumentHelper;
-import dan200.computercraft.api.lua.ILuaContext;
-import dan200.computercraft.api.lua.ILuaObject;
-import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.*;
import dan200.computercraft.core.tracking.TrackingField;
import dan200.computercraft.shared.util.StringUtil;
import io.netty.buffer.Unpooled;
@@ -19,16 +16,16 @@
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
import java.io.Closeable;
import java.util.Arrays;
+import java.util.Optional;
-import static dan200.computercraft.api.lua.ArgumentHelper.optBoolean;
+import static dan200.computercraft.api.lua.LuaValues.checkFinite;
import static dan200.computercraft.core.apis.IAPIEnvironment.TIMER_EVENT;
import static dan200.computercraft.core.apis.http.websocket.Websocket.CLOSE_EVENT;
import static dan200.computercraft.core.apis.http.websocket.Websocket.MESSAGE_EVENT;
-public class WebsocketHandle implements ILuaObject, Closeable
+public class WebsocketHandle implements Closeable
{
private final Websocket websocket;
private boolean closed = false;
@@ -41,87 +38,45 @@ public WebsocketHandle( Websocket websocket, Channel channel )
this.channel = channel;
}
- @Nonnull
- @Override
- public String[] getMethodNames()
+ @LuaFunction
+ public final MethodResult result( Optional timeout ) throws LuaException
{
- return new String[] { "receive", "send", "close" };
+ checkOpen();
+ int timeoutId = timeout.isPresent()
+ ? websocket.environment().startTimer( Math.round( checkFinite( 0, timeout.get() ) / 0.05 ) )
+ : -1;
+
+ return new ReceiveCallback( timeoutId ).pull;
}
- @Nullable
- @Override
- public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
+ @LuaFunction
+ public final void send( IArguments args ) throws LuaException
{
- switch( method )
+ checkOpen();
+
+ String text = StringUtil.toString( args.get( 0 ) );
+ if( ComputerCraft.httpMaxWebsocketMessage != 0 && text.length() > ComputerCraft.httpMaxWebsocketMessage )
{
- case 0: // receive
- {
- checkOpen();
- int timeoutId;
- if( arguments.length <= 0 || arguments[0] == null )
- {
- // We do this rather odd argument validation to ensure we can tell the difference between a
- // negative timeout and an absent one.
- timeoutId = -1;
- }
- else
- {
- double timeout = ArgumentHelper.getFiniteDouble( arguments, 0 );
- timeoutId = websocket.environment().startTimer( Math.round( timeout / 0.05 ) );
- }
-
- while( true )
- {
- Object[] event = context.pullEvent( null );
- if( event.length >= 3 && Objects.equal( event[0], MESSAGE_EVENT ) && Objects.equal( event[1], websocket.address() ) )
- {
- return Arrays.copyOfRange( event, 2, event.length );
- }
- else if( event.length >= 2 && Objects.equal( event[0], CLOSE_EVENT ) && Objects.equal( event[1], websocket.address() ) && closed )
- {
- // If the socket is closed abort.
- return null;
- }
- else if( event.length >= 2 && timeoutId != -1 && Objects.equal( event[0], TIMER_EVENT )
- && event[1] instanceof Number && ((Number) event[1]).intValue() == timeoutId )
- {
- // If we received a matching timer event then abort.
- return null;
- }
- }
- }
-
- case 1: // send
- {
- checkOpen();
-
- String text = arguments.length > 0 && arguments[0] != null ? arguments[0].toString() : "";
- if( ComputerCraft.httpMaxWebsocketMessage != 0 && text.length() > ComputerCraft.httpMaxWebsocketMessage )
- {
- throw new LuaException( "Message is too large" );
- }
-
- boolean binary = optBoolean( arguments, 1, false );
- websocket.environment().addTrackingChange( TrackingField.WEBSOCKET_OUTGOING, text.length() );
-
- Channel channel = this.channel;
- if( channel != null )
- {
- channel.writeAndFlush( binary
- ? new BinaryWebSocketFrame( Unpooled.wrappedBuffer( StringUtil.encodeString( text ) ) )
- : new TextWebSocketFrame( text ) );
- }
-
- return null;
- }
-
- case 2: // close
- close();
- websocket.close();
- return null;
- default:
- return null;
+ throw new LuaException( "Message is too large" );
}
+
+ boolean binary = args.optBoolean( 1, false );
+ websocket.environment().addTrackingChange( TrackingField.WEBSOCKET_OUTGOING, text.length() );
+
+ Channel channel = this.channel;
+ if( channel != null )
+ {
+ channel.writeAndFlush( binary
+ ? new BinaryWebSocketFrame( Unpooled.wrappedBuffer( LuaValues.encode( text ) ) )
+ : new TextWebSocketFrame( text ) );
+ }
+ }
+
+ @LuaFunction( "close" )
+ public final void doClose()
+ {
+ close();
+ websocket.close();
}
private void checkOpen() throws LuaException
@@ -141,4 +96,38 @@ public void close()
this.channel = null;
}
}
+
+ private final class ReceiveCallback implements ILuaCallback
+ {
+ final MethodResult pull = MethodResult.pullEvent( null, this );
+ private final int timeoutId;
+
+ ReceiveCallback( int timeoutId )
+ {
+ this.timeoutId = timeoutId;
+ }
+
+ @Nonnull
+ @Override
+ public MethodResult resume( Object[] event )
+ {
+ if( event.length >= 3 && Objects.equal( event[0], MESSAGE_EVENT ) && Objects.equal( event[1], websocket.address() ) )
+ {
+ return MethodResult.of( Arrays.copyOfRange( event, 2, event.length ) );
+ }
+ else if( event.length >= 2 && Objects.equal( event[0], CLOSE_EVENT ) && Objects.equal( event[1], websocket.address() ) && closed )
+ {
+ // If the socket is closed abort.
+ return MethodResult.of();
+ }
+ else if( event.length >= 2 && timeoutId != -1 && Objects.equal( event[0], TIMER_EVENT )
+ && event[1] instanceof Number && ((Number) event[1]).intValue() == timeoutId )
+ {
+ // If we received a matching timer event then abort.
+ return MethodResult.of();
+ }
+
+ return pull;
+ }
+ }
}
diff --git a/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketHandler.java b/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketHandler.java
index 70a2e77a6..c3e69d7b9 100644
--- a/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketHandler.java
+++ b/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketHandler.java
@@ -68,14 +68,14 @@ public void channelRead0( ChannelHandlerContext ctx, Object msg )
String data = ((TextWebSocketFrame) frame).text();
websocket.environment().addTrackingChange( TrackingField.WEBSOCKET_INCOMING, data.length() );
- websocket.environment().queueEvent( MESSAGE_EVENT, new Object[] { websocket.address(), data, false } );
+ websocket.environment().queueEvent( MESSAGE_EVENT, websocket.address(), data, false );
}
else if( frame instanceof BinaryWebSocketFrame )
{
byte[] converted = NetworkUtils.toBytes( frame.content() );
websocket.environment().addTrackingChange( TrackingField.WEBSOCKET_INCOMING, converted.length );
- websocket.environment().queueEvent( MESSAGE_EVENT, new Object[] { websocket.address(), converted, true } );
+ websocket.environment().queueEvent( MESSAGE_EVENT, websocket.address(), converted, true );
}
else if( frame instanceof CloseWebSocketFrame )
{
diff --git a/src/main/java/dan200/computercraft/core/asm/DeclaringClassLoader.java b/src/main/java/dan200/computercraft/core/asm/DeclaringClassLoader.java
new file mode 100644
index 000000000..009a8840e
--- /dev/null
+++ b/src/main/java/dan200/computercraft/core/asm/DeclaringClassLoader.java
@@ -0,0 +1,24 @@
+/*
+ * This file is part of ComputerCraft - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
+ * Send enquiries to dratcliffe@gmail.com
+ */
+
+package dan200.computercraft.core.asm;
+
+import java.security.ProtectionDomain;
+
+final class DeclaringClassLoader extends ClassLoader
+{
+ static final DeclaringClassLoader INSTANCE = new DeclaringClassLoader();
+
+ private DeclaringClassLoader()
+ {
+ super( DeclaringClassLoader.class.getClassLoader() );
+ }
+
+ Class> define( String name, byte[] bytes, ProtectionDomain protectionDomain ) throws ClassFormatError
+ {
+ return defineClass( name, bytes, 0, bytes.length, protectionDomain );
+ }
+}
diff --git a/src/main/java/dan200/computercraft/core/asm/Generator.java b/src/main/java/dan200/computercraft/core/asm/Generator.java
new file mode 100644
index 000000000..801969d0c
--- /dev/null
+++ b/src/main/java/dan200/computercraft/core/asm/Generator.java
@@ -0,0 +1,320 @@
+/*
+ * This file is part of ComputerCraft - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
+ * Send enquiries to dratcliffe@gmail.com
+ */
+
+package dan200.computercraft.core.asm;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.primitives.Primitives;
+import com.google.common.reflect.TypeToken;
+import dan200.computercraft.ComputerCraft;
+import dan200.computercraft.api.lua.IArguments;
+import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.LuaFunction;
+import dan200.computercraft.api.lua.MethodResult;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Type;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
+
+import static org.objectweb.asm.Opcodes.*;
+
+public class Generator
+{
+ private static final AtomicInteger METHOD_ID = new AtomicInteger();
+
+ private static final String METHOD_NAME = "apply";
+ private static final String[] EXCEPTIONS = new String[] { Type.getInternalName( LuaException.class ) };
+
+ private static final String INTERNAL_METHOD_RESULT = Type.getInternalName( MethodResult.class );
+ private static final String DESC_METHOD_RESULT = Type.getDescriptor( MethodResult.class );
+
+ private static final String INTERNAL_ARGUMENTS = Type.getInternalName( IArguments.class );
+ private static final String DESC_ARGUMENTS = Type.getDescriptor( IArguments.class );
+
+ private final Class base;
+ private final List> context;
+
+ private final String[] interfaces;
+ private final String methodDesc;
+
+ private final Function wrap;
+
+ private final LoadingCache, List>> classCache = CacheBuilder
+ .newBuilder()
+ .build( CacheLoader.from( this::build ) );
+
+ private final LoadingCache> methodCache = CacheBuilder
+ .newBuilder()
+ .build( CacheLoader.from( this::build ) );
+
+ Generator( Class base, List> context, Function wrap )
+ {
+ this.base = base;
+ this.context = context;
+ this.interfaces = new String[] { Type.getInternalName( base ) };
+ this.wrap = wrap;
+
+ StringBuilder methodDesc = new StringBuilder().append( "(Ljava/lang/Object;" );
+ for( Class> klass : context ) methodDesc.append( Type.getDescriptor( klass ) );
+ methodDesc.append( DESC_ARGUMENTS ).append( ")" ).append( DESC_METHOD_RESULT );
+ this.methodDesc = methodDesc.toString();
+ }
+
+ @Nonnull
+ public List> getMethods( @Nonnull Class> klass )
+ {
+ try
+ {
+ return classCache.get( klass );
+ }
+ catch( ExecutionException e )
+ {
+ ComputerCraft.log.error( "Error getting methods for {}.", klass.getName(), e.getCause() );
+ return Collections.emptyList();
+ }
+ }
+
+ @Nonnull
+ private List> build( Class> klass )
+ {
+ ArrayList> methods = null;
+ for( Method method : klass.getMethods() )
+ {
+ LuaFunction annotation = method.getAnnotation( LuaFunction.class );
+ if( annotation == null ) continue;
+
+ T instance = methodCache.getUnchecked( method ).orElse( null );
+ if( instance == null ) continue;
+
+ if( methods == null ) methods = new ArrayList<>();
+
+ if( annotation.mainThread() ) instance = wrap.apply( instance );
+
+ String[] names = annotation.value();
+ boolean isSimple = method.getReturnType() != MethodResult.class && !annotation.mainThread();
+ if( names.length == 0 )
+ {
+ methods.add( new NamedMethod<>( method.getName(), instance, isSimple ) );
+ }
+ else
+ {
+ for( String name : names )
+ {
+ methods.add( new NamedMethod<>( name, instance, isSimple ) );
+ }
+ }
+ }
+
+ if( methods == null ) return Collections.emptyList();
+ methods.trimToSize();
+ return Collections.unmodifiableList( methods );
+ }
+
+ @Nonnull
+ private Optional build( Method method )
+ {
+ String name = method.getDeclaringClass().getName() + "." + method.getName();
+ int modifiers = method.getModifiers();
+ if( !Modifier.isFinal( modifiers ) )
+ {
+ ComputerCraft.log.warn( "Lua Method {} should be final.", name );
+ }
+
+ if( Modifier.isStatic( modifiers ) || !Modifier.isPublic( modifiers ) )
+ {
+ ComputerCraft.log.error( "Lua Method {} should be a public instance method.", name );
+ return Optional.empty();
+ }
+
+ if( !Modifier.isPublic( method.getDeclaringClass().getModifiers() ) )
+ {
+ ComputerCraft.log.error( "Lua Method {} should be on a public class.", name );
+ return Optional.empty();
+ }
+
+ ComputerCraft.log.debug( "Generating method wrapper for {}.", name );
+
+ Class>[] exceptions = method.getExceptionTypes();
+ for( Class> exception : exceptions )
+ {
+ if( exception != LuaException.class )
+ {
+ ComputerCraft.log.error( "Lua Method {} cannot throw {}.", name, exception.getName() );
+ return Optional.empty();
+ }
+ }
+
+ try
+ {
+ String className = method.getDeclaringClass().getName() + "$cc$" + method.getName() + METHOD_ID.getAndIncrement();
+ byte[] bytes = generate( className, method );
+ if( bytes == null ) return Optional.empty();
+
+ Class> klass = DeclaringClassLoader.INSTANCE.define( className, bytes, method.getDeclaringClass().getProtectionDomain() );
+ return Optional.of( klass.asSubclass( base ).newInstance() );
+ }
+ catch( InstantiationException | IllegalAccessException | ClassFormatError | RuntimeException e )
+ {
+ ComputerCraft.log.error( "Error generating wrapper for {}.", name, e );
+ return Optional.empty();
+ }
+
+ }
+
+ @Nullable
+ private byte[] generate( String className, Method method )
+ {
+ String internalName = className.replace( ".", "/" );
+
+ // Construct a public final class which extends Object and implements MethodInstance.Delegate
+ ClassWriter cw = new ClassWriter( ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS );
+ cw.visit( V1_8, ACC_PUBLIC | ACC_FINAL, internalName, null, "java/lang/Object", interfaces );
+ cw.visitSource( "CC generated method", null );
+
+ { // Constructor just invokes super.
+ MethodVisitor mw = cw.visitMethod( ACC_PUBLIC, "", "()V", null, null );
+ mw.visitCode();
+ mw.visitVarInsn( ALOAD, 0 );
+ mw.visitMethodInsn( INVOKESPECIAL, "java/lang/Object", "", "()V", false );
+ mw.visitInsn( RETURN );
+ mw.visitMaxs( 0, 0 );
+ mw.visitEnd();
+ }
+
+ {
+ MethodVisitor mw = cw.visitMethod( ACC_PUBLIC, METHOD_NAME, methodDesc, null, EXCEPTIONS );
+ mw.visitCode();
+ mw.visitVarInsn( ALOAD, 1 );
+ mw.visitTypeInsn( CHECKCAST, Type.getInternalName( method.getDeclaringClass() ) );
+
+ int argIndex = 0;
+ for( java.lang.reflect.Type genericArg : method.getGenericParameterTypes() )
+ {
+ Boolean loadedArg = loadArg( mw, method, genericArg, argIndex );
+ if( loadedArg == null ) return null;
+ if( loadedArg ) argIndex++;
+ }
+
+ mw.visitMethodInsn( INVOKEVIRTUAL, Type.getInternalName( method.getDeclaringClass() ), method.getName(),
+ Type.getMethodDescriptor( method ), false );
+
+ // We allow a reasonable amount of flexibility on the return value's type. Alongside the obvious MethodResult,
+ // we convert basic types into an immediate result.
+ Class> ret = method.getReturnType();
+ if( ret != MethodResult.class )
+ {
+ if( ret == void.class )
+ {
+ mw.visitMethodInsn( INVOKESTATIC, INTERNAL_METHOD_RESULT, "of", "()" + DESC_METHOD_RESULT, false );
+ }
+ else if( ret.isPrimitive() )
+ {
+ Class> boxed = Primitives.wrap( ret );
+ mw.visitMethodInsn( INVOKESTATIC, Type.getInternalName( boxed ), "valueOf", "(" + Type.getDescriptor( ret ) + ")" + Type.getDescriptor( boxed ), false );
+ mw.visitMethodInsn( INVOKESTATIC, INTERNAL_METHOD_RESULT, "of", "(Ljava/lang/Object;)" + DESC_METHOD_RESULT, false );
+ }
+ else if( ret == Object[].class )
+ {
+ mw.visitMethodInsn( INVOKESTATIC, INTERNAL_METHOD_RESULT, "of", "([Ljava/lang/Object;)" + DESC_METHOD_RESULT, false );
+ }
+ else
+ {
+ mw.visitMethodInsn( INVOKESTATIC, INTERNAL_METHOD_RESULT, "of", "(Ljava/lang/Object;)" + DESC_METHOD_RESULT, false );
+ }
+ }
+
+ mw.visitInsn( ARETURN );
+
+ mw.visitMaxs( 0, 0 );
+ mw.visitEnd();
+ }
+
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ private Boolean loadArg( MethodVisitor mw, Method method, java.lang.reflect.Type genericArg, int argIndex )
+ {
+ Class> arg = Reflect.getRawType( method, genericArg, true );
+ if( arg == null ) return null;
+
+ if( arg == IArguments.class )
+ {
+ mw.visitVarInsn( ALOAD, 2 + context.size() );
+ return false;
+ }
+
+ int idx = context.indexOf( arg );
+ if( idx >= 0 )
+ {
+ mw.visitVarInsn( ALOAD, 2 + idx );
+ return false;
+ }
+
+ if( arg == Optional.class )
+ {
+ Class> klass = Reflect.getRawType( method, TypeToken.of( genericArg ).resolveType( Reflect.OPTIONAL_IN ).getType(), false );
+ if( klass == null ) return null;
+
+ if( Enum.class.isAssignableFrom( klass ) && klass != Enum.class )
+ {
+ mw.visitVarInsn( ALOAD, 2 + context.size() );
+ Reflect.loadInt( mw, argIndex );
+ mw.visitLdcInsn( Type.getType( klass ) );
+ mw.visitMethodInsn( INVOKEINTERFACE, INTERNAL_ARGUMENTS, "optEnum", "(ILjava/lang/Class;)Ljava/util/Optional;", true );
+ return true;
+ }
+
+ String name = Reflect.getLuaName( Primitives.unwrap( klass ) );
+ if( name != null )
+ {
+ mw.visitVarInsn( ALOAD, 2 + context.size() );
+ Reflect.loadInt( mw, argIndex );
+ mw.visitMethodInsn( INVOKEINTERFACE, INTERNAL_ARGUMENTS, "opt" + name, "(I)Ljava/util/Optional;", true );
+ return true;
+ }
+ }
+
+ if( Enum.class.isAssignableFrom( arg ) && arg != Enum.class )
+ {
+ mw.visitVarInsn( ALOAD, 2 + context.size() );
+ Reflect.loadInt( mw, argIndex );
+ mw.visitLdcInsn( Type.getType( arg ) );
+ mw.visitMethodInsn( INVOKEINTERFACE, INTERNAL_ARGUMENTS, "getEnum", "(ILjava/lang/Class;)Ljava/lang/Enum;", true );
+ mw.visitTypeInsn( CHECKCAST, Type.getInternalName( arg ) );
+ return true;
+ }
+
+ String name = arg == Object.class ? "" : Reflect.getLuaName( arg );
+ if( name != null )
+ {
+ if( Reflect.getRawType( method, genericArg, false ) == null ) return null;
+
+ mw.visitVarInsn( ALOAD, 2 + context.size() );
+ Reflect.loadInt( mw, argIndex );
+ mw.visitMethodInsn( INVOKEINTERFACE, INTERNAL_ARGUMENTS, "get" + name, "(I)" + Type.getDescriptor( arg ), true );
+ return true;
+ }
+
+ ComputerCraft.log.error( "Unknown parameter type {} for method {}.{}.",
+ arg.getName(), method.getDeclaringClass().getName(), method.getName() );
+ return null;
+ }
+}
diff --git a/src/main/java/dan200/computercraft/core/asm/IntCache.java b/src/main/java/dan200/computercraft/core/asm/IntCache.java
new file mode 100644
index 000000000..d50f54b2a
--- /dev/null
+++ b/src/main/java/dan200/computercraft/core/asm/IntCache.java
@@ -0,0 +1,41 @@
+/*
+ * This file is part of ComputerCraft - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
+ * Send enquiries to dratcliffe@gmail.com
+ */
+
+package dan200.computercraft.core.asm;
+
+import java.util.Arrays;
+import java.util.function.IntFunction;
+
+public final class IntCache
+{
+ private final IntFunction factory;
+ private volatile Object[] cache = new Object[16];
+
+ IntCache( IntFunction factory )
+ {
+ this.factory = factory;
+ }
+
+ @SuppressWarnings( "unchecked" )
+ public T get( int index )
+ {
+ if( index < 0 ) throw new IllegalArgumentException( "index < 0" );
+
+ if( index <= cache.length )
+ {
+ T current = (T) cache[index];
+ if( current != null ) return current;
+ }
+
+ synchronized( this )
+ {
+ if( index > cache.length ) cache = Arrays.copyOf( cache, Math.max( cache.length * 2, index ) );
+ T current = (T) cache[index];
+ if( current == null ) cache[index] = current = factory.apply( index );
+ return current;
+ }
+ }
+}
diff --git a/src/main/java/dan200/computercraft/core/asm/LuaMethod.java b/src/main/java/dan200/computercraft/core/asm/LuaMethod.java
new file mode 100644
index 000000000..35ad33b76
--- /dev/null
+++ b/src/main/java/dan200/computercraft/core/asm/LuaMethod.java
@@ -0,0 +1,30 @@
+/*
+ * This file is part of ComputerCraft - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
+ * Send enquiries to dratcliffe@gmail.com
+ */
+
+package dan200.computercraft.core.asm;
+
+import dan200.computercraft.api.lua.*;
+
+import javax.annotation.Nonnull;
+import java.util.Collections;
+
+public interface LuaMethod
+{
+ Generator GENERATOR = new Generator<>( LuaMethod.class, Collections.singletonList( ILuaContext.class ),
+ m -> ( target, context, args ) -> {
+ long id = context.issueMainThreadTask( () -> TaskCallback.checkUnwrap( m.apply( target, context, args ) ) );
+ return new TaskCallback( id ).pull;
+ } );
+
+ IntCache DYNAMIC = new IntCache<>(
+ method -> ( instance, context, args ) -> ((IDynamicLuaObject) instance).callMethod( context, method, args )
+ );
+
+ String[] EMPTY_METHODS = new String[0];
+
+ @Nonnull
+ MethodResult apply( @Nonnull Object target, @Nonnull ILuaContext context, @Nonnull IArguments args ) throws LuaException;
+}
diff --git a/src/main/java/dan200/computercraft/core/asm/NamedMethod.java b/src/main/java/dan200/computercraft/core/asm/NamedMethod.java
new file mode 100644
index 000000000..d7525fc7f
--- /dev/null
+++ b/src/main/java/dan200/computercraft/core/asm/NamedMethod.java
@@ -0,0 +1,40 @@
+/*
+ * This file is part of ComputerCraft - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
+ * Send enquiries to dratcliffe@gmail.com
+ */
+
+package dan200.computercraft.core.asm;
+
+import javax.annotation.Nonnull;
+
+public class NamedMethod
+{
+ private final String name;
+ private final T method;
+ private final boolean nonYielding;
+
+ NamedMethod( String name, T method, boolean nonYielding )
+ {
+ this.name = name;
+ this.method = method;
+ this.nonYielding = nonYielding;
+ }
+
+ @Nonnull
+ public String getName()
+ {
+ return name;
+ }
+
+ @Nonnull
+ public T getMethod()
+ {
+ return method;
+ }
+
+ public boolean nonYielding()
+ {
+ return nonYielding;
+ }
+}
diff --git a/src/main/java/dan200/computercraft/core/asm/ObjectSource.java b/src/main/java/dan200/computercraft/core/asm/ObjectSource.java
new file mode 100644
index 000000000..f05ac7070
--- /dev/null
+++ b/src/main/java/dan200/computercraft/core/asm/ObjectSource.java
@@ -0,0 +1,33 @@
+/*
+ * This file is part of ComputerCraft - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
+ * Send enquiries to dratcliffe@gmail.com
+ */
+
+package dan200.computercraft.core.asm;
+
+import java.util.function.BiConsumer;
+
+/**
+ * A Lua object which exposes additional methods.
+ *
+ * This can be used to merge multiple objects together into one. Ideally this'd be part of the API, but I'm not entirely
+ * happy with the interface - something I'd like to think about first.
+ */
+public interface ObjectSource
+{
+ Iterable getExtra();
+
+ static void allMethods( Generator generator, Object object, BiConsumer> accept )
+ {
+ for( NamedMethod method : generator.getMethods( object.getClass() ) ) accept.accept( object, method );
+
+ if( object instanceof ObjectSource )
+ {
+ for( Object extra : ((ObjectSource) object).getExtra() )
+ {
+ for( NamedMethod method : generator.getMethods( extra.getClass() ) ) accept.accept( extra, method );
+ }
+ }
+ }
+}
diff --git a/src/main/java/dan200/computercraft/core/asm/PeripheralMethod.java b/src/main/java/dan200/computercraft/core/asm/PeripheralMethod.java
new file mode 100644
index 000000000..637cfb806
--- /dev/null
+++ b/src/main/java/dan200/computercraft/core/asm/PeripheralMethod.java
@@ -0,0 +1,33 @@
+/*
+ * This file is part of ComputerCraft - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
+ * Send enquiries to dratcliffe@gmail.com
+ */
+
+package dan200.computercraft.core.asm;
+
+import dan200.computercraft.api.lua.IArguments;
+import dan200.computercraft.api.lua.ILuaContext;
+import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.MethodResult;
+import dan200.computercraft.api.peripheral.IComputerAccess;
+import dan200.computercraft.api.peripheral.IDynamicPeripheral;
+
+import javax.annotation.Nonnull;
+import java.util.Arrays;
+
+public interface PeripheralMethod
+{
+ Generator GENERATOR = new Generator<>( PeripheralMethod.class, Arrays.asList( ILuaContext.class, IComputerAccess.class ),
+ m -> ( target, context, computer, args ) -> {
+ long id = context.issueMainThreadTask( () -> TaskCallback.checkUnwrap( m.apply( target, context, computer, args ) ) );
+ return new TaskCallback( id ).pull;
+ } );
+
+ IntCache DYNAMIC = new IntCache<>(
+ method -> ( instance, context, computer, args ) -> ((IDynamicPeripheral) instance).callMethod( computer, context, method, args )
+ );
+
+ @Nonnull
+ MethodResult apply( @Nonnull Object target, @Nonnull ILuaContext context, @Nonnull IComputerAccess computer, @Nonnull IArguments args ) throws LuaException;
+}
diff --git a/src/main/java/dan200/computercraft/core/asm/Reflect.java b/src/main/java/dan200/computercraft/core/asm/Reflect.java
new file mode 100644
index 000000000..429402206
--- /dev/null
+++ b/src/main/java/dan200/computercraft/core/asm/Reflect.java
@@ -0,0 +1,95 @@
+/*
+ * This file is part of ComputerCraft - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
+ * Send enquiries to dratcliffe@gmail.com
+ */
+
+package dan200.computercraft.core.asm;
+
+import dan200.computercraft.ComputerCraft;
+import org.objectweb.asm.MethodVisitor;
+
+import javax.annotation.Nullable;
+import java.lang.reflect.*;
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.Optional;
+
+import static org.objectweb.asm.Opcodes.ICONST_0;
+
+final class Reflect
+{
+ static final java.lang.reflect.Type OPTIONAL_IN = Optional.class.getTypeParameters()[0];
+
+ private Reflect()
+ {
+ }
+
+ @Nullable
+ static String getLuaName( Class> klass )
+ {
+ if( klass.isPrimitive() )
+ {
+ if( klass == int.class ) return "Int";
+ if( klass == boolean.class ) return "Boolean";
+ if( klass == double.class ) return "Double";
+ if( klass == long.class ) return "Long";
+ }
+ else
+ {
+ if( klass == Map.class ) return "Table";
+ if( klass == String.class ) return "String";
+ if( klass == ByteBuffer.class ) return "Bytes";
+ }
+
+ return null;
+ }
+
+ @Nullable
+ static Class> getRawType( Method method, Type root, boolean allowParameter )
+ {
+ Type underlying = root;
+ while( true )
+ {
+ if( underlying instanceof Class> ) return (Class>) underlying;
+
+ if( underlying instanceof ParameterizedType )
+ {
+ ParameterizedType type = (ParameterizedType) underlying;
+ if( !allowParameter )
+ {
+ for( java.lang.reflect.Type arg : type.getActualTypeArguments() )
+ {
+ if( arg instanceof WildcardType ) continue;
+ if( arg instanceof TypeVariable && ((TypeVariable>) arg).getName().startsWith( "capture#" ) )
+ {
+ continue;
+ }
+
+ ComputerCraft.log.error( "Method {}.{} has generic type {} with non-wildcard argument {}.", method.getDeclaringClass(), method.getName(), root, arg );
+ return null;
+ }
+ }
+
+ // Continue to extract from this child
+ underlying = type.getRawType();
+ continue;
+ }
+
+ ComputerCraft.log.error( "Method {}.{} has unknown generic type {}.", method.getDeclaringClass(), method.getName(), root );
+ return null;
+ }
+ }
+
+ static void loadInt( MethodVisitor visitor, int value )
+ {
+ if( value >= -1 && value <= 5 )
+ {
+ visitor.visitInsn( ICONST_0 + value );
+ }
+ else
+ {
+ visitor.visitLdcInsn( value );
+ }
+ }
+}
diff --git a/src/main/java/dan200/computercraft/core/asm/TaskCallback.java b/src/main/java/dan200/computercraft/core/asm/TaskCallback.java
new file mode 100644
index 000000000..9ea4e67ad
--- /dev/null
+++ b/src/main/java/dan200/computercraft/core/asm/TaskCallback.java
@@ -0,0 +1,58 @@
+/*
+ * This file is part of ComputerCraft - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
+ * Send enquiries to dratcliffe@gmail.com
+ */
+
+package dan200.computercraft.core.asm;
+
+import dan200.computercraft.api.lua.ILuaCallback;
+import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.MethodResult;
+
+import javax.annotation.Nonnull;
+import java.util.Arrays;
+
+class TaskCallback implements ILuaCallback
+{
+ final MethodResult pull = MethodResult.pullEvent( "task_complete", this );
+ private final long task;
+
+ TaskCallback( long task )
+ {
+ this.task = task;
+ }
+
+ @Nonnull
+ @Override
+ public MethodResult resume( Object[] response ) throws LuaException
+ {
+ if( response.length < 3 || !(response[1] instanceof Number) || !(response[2] instanceof Boolean) )
+ {
+ return pull;
+ }
+
+ if( ((Number) response[1]).longValue() != task ) return pull;
+
+ if( (Boolean) response[2] )
+ {
+ // Extract the return values from the event and return them
+ return MethodResult.of( Arrays.copyOfRange( response, 3, response.length ) );
+ }
+ else if( response.length >= 4 && response[3] instanceof String )
+ {
+ // Extract the error message from the event and raise it
+ throw new LuaException( (String) response[3] );
+ }
+ else
+ {
+ throw new LuaException( "error" );
+ }
+ }
+
+ public static Object[] checkUnwrap( MethodResult result )
+ {
+ if( result.getCallback() != null ) throw new IllegalStateException( "Cannot return MethodResult currently" );
+ return result.getResult();
+ }
+}
diff --git a/src/main/java/dan200/computercraft/core/computer/ApiWrapper.java b/src/main/java/dan200/computercraft/core/computer/ApiWrapper.java
index 96be03c3e..693045dfe 100644
--- a/src/main/java/dan200/computercraft/core/computer/ApiWrapper.java
+++ b/src/main/java/dan200/computercraft/core/computer/ApiWrapper.java
@@ -6,11 +6,6 @@
package dan200.computercraft.core.computer;
import dan200.computercraft.api.lua.ILuaAPI;
-import dan200.computercraft.api.lua.ILuaContext;
-import dan200.computercraft.api.lua.LuaException;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
/**
* A wrapper for {@link ILuaAPI}s which cleans up after a {@link ComputerSystem} when the computer is shutdown.
@@ -51,17 +46,8 @@ public void shutdown()
system.unmountAll();
}
- @Nonnull
- @Override
- public String[] getMethodNames()
+ public ILuaAPI getDelegate()
{
- return delegate.getMethodNames();
- }
-
- @Nullable
- @Override
- public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
- {
- return delegate.callMethod( context, method, arguments );
+ return delegate;
}
}
diff --git a/src/main/java/dan200/computercraft/core/computer/Computer.java b/src/main/java/dan200/computercraft/core/computer/Computer.java
index 55f02086d..8affb0f01 100644
--- a/src/main/java/dan200/computercraft/core/computer/Computer.java
+++ b/src/main/java/dan200/computercraft/core/computer/Computer.java
@@ -44,7 +44,7 @@ public class Computer
// Additional state about the computer and its environment.
private boolean m_blinking = false;
private final Environment internalEnvironment = new Environment( this );
- private AtomicBoolean externalOutputChanged = new AtomicBoolean();
+ private final AtomicBoolean externalOutputChanged = new AtomicBoolean();
private boolean startRequested;
private int m_ticksSinceStart = -1;
diff --git a/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java b/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java
index 811168b80..021c4b473 100644
--- a/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java
+++ b/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java
@@ -388,8 +388,8 @@ private ILuaMachine createLuaMachine()
// Create the lua machine
ILuaMachine machine = new CobaltLuaMachine( computer, timeout );
- // Add the APIs
- for( ILuaAPI api : apis ) machine.addAPI( api );
+ // Add the APIs. We unwrap them (yes, this is horrible) to get access to the underlying object.
+ for( ILuaAPI api : apis ) machine.addAPI( api instanceof ApiWrapper ? ((ApiWrapper) api).getDelegate() : api );
// Start the machine running the bios resource
MachineResult result = machine.loadBios( biosStream );
diff --git a/src/main/java/dan200/computercraft/core/computer/Environment.java b/src/main/java/dan200/computercraft/core/computer/Environment.java
index 819997c92..041973fef 100644
--- a/src/main/java/dan200/computercraft/core/computer/Environment.java
+++ b/src/main/java/dan200/computercraft/core/computer/Environment.java
@@ -111,7 +111,7 @@ public void reboot()
}
@Override
- public void queueEvent( String event, Object[] args )
+ public void queueEvent( String event, Object... args )
{
computer.queueEvent( event, args );
}
@@ -226,7 +226,7 @@ void tick()
if( inputChanged )
{
inputChanged = false;
- queueEvent( "redstone", null );
+ queueEvent( "redstone" );
}
synchronized( timers )
@@ -241,7 +241,7 @@ void tick()
if( timer.ticksLeft <= 0 )
{
// Queue the "timer" event
- queueEvent( TIMER_EVENT, new Object[] { entry.getIntKey() } );
+ queueEvent( TIMER_EVENT, entry.getIntKey() );
it.remove();
}
}
diff --git a/src/main/java/dan200/computercraft/core/lua/BasicFunction.java b/src/main/java/dan200/computercraft/core/lua/BasicFunction.java
new file mode 100644
index 000000000..3c988f025
--- /dev/null
+++ b/src/main/java/dan200/computercraft/core/lua/BasicFunction.java
@@ -0,0 +1,75 @@
+/*
+ * This file is part of ComputerCraft - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
+ * Send enquiries to dratcliffe@gmail.com
+ */
+
+package dan200.computercraft.core.lua;
+
+import dan200.computercraft.ComputerCraft;
+import dan200.computercraft.api.lua.IArguments;
+import dan200.computercraft.api.lua.ILuaContext;
+import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.MethodResult;
+import dan200.computercraft.core.asm.LuaMethod;
+import org.squiddev.cobalt.LuaError;
+import org.squiddev.cobalt.LuaState;
+import org.squiddev.cobalt.Varargs;
+import org.squiddev.cobalt.function.VarArgFunction;
+
+/**
+ * An "optimised" version of {@link ResultInterpreterFunction} which is guaranteed to never yield.
+ *
+ * As we never yield, we do not need to push a function to the stack, which removes a small amount of overhead.
+ */
+class BasicFunction extends VarArgFunction
+{
+ private final CobaltLuaMachine machine;
+ private final LuaMethod method;
+ private final Object instance;
+ private final ILuaContext context;
+ private final String name;
+
+ BasicFunction( CobaltLuaMachine machine, LuaMethod method, Object instance, ILuaContext context, String name )
+ {
+ this.machine = machine;
+ this.method = method;
+ this.instance = instance;
+ this.context = context;
+ this.name = name;
+ }
+
+ @Override
+ public Varargs invoke( LuaState luaState, Varargs args ) throws LuaError
+ {
+ IArguments arguments = CobaltLuaMachine.toArguments( args );
+ MethodResult results;
+ try
+ {
+ results = method.apply( instance, context, arguments );
+ }
+ catch( LuaException e )
+ {
+ throw wrap( e );
+ }
+ catch( Throwable t )
+ {
+ if( ComputerCraft.logPeripheralErrors )
+ {
+ ComputerCraft.log.error( "Error calling " + name + " on " + instance, t );
+ }
+ throw new LuaError( "Java Exception Thrown: " + t, 0 );
+ }
+
+ if( results.getCallback() != null )
+ {
+ throw new IllegalStateException( "Cannot have a yielding non-yielding function" );
+ }
+ return machine.toValues( results.getResult() );
+ }
+
+ public static LuaError wrap( LuaException exception )
+ {
+ return exception.hasLevel() ? new LuaError( exception.getMessage() ) : new LuaError( exception.getMessage(), exception.getLevel() );
+ }
+}
diff --git a/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java b/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java
index 8e5e88bb0..dc8291b21 100644
--- a/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java
+++ b/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java
@@ -7,6 +7,8 @@
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.*;
+import dan200.computercraft.core.asm.LuaMethod;
+import dan200.computercraft.core.asm.ObjectSource;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.MainThread;
import dan200.computercraft.core.computer.TimeoutState;
@@ -20,13 +22,13 @@
import org.squiddev.cobalt.debug.DebugHandler;
import org.squiddev.cobalt.debug.DebugState;
import org.squiddev.cobalt.function.LuaFunction;
-import org.squiddev.cobalt.function.VarArgFunction;
import org.squiddev.cobalt.lib.*;
import org.squiddev.cobalt.lib.platform.VoidResourceManipulator;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.InputStream;
+import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
@@ -116,11 +118,14 @@ public void addAPI( @Nonnull ILuaAPI api )
{
// Add the methods of an API to the global table
LuaTable table = wrapLuaObject( api );
- String[] names = api.getNames();
- for( String name : names )
+ if( table == null )
{
- m_globals.rawset( name, table );
+ ComputerCraft.log.warn( "API {} does not provide any methods", api );
+ table = new LuaTable();
}
+
+ String[] names = api.getNames();
+ for( String name : names ) m_globals.rawset( name, table );
}
@Override
@@ -216,54 +221,38 @@ public void close()
m_globals = null;
}
- private LuaTable wrapLuaObject( ILuaObject object )
+ @Nullable
+ private LuaTable wrapLuaObject( Object object )
{
+ String[] dynamicMethods = object instanceof IDynamicLuaObject
+ ? Objects.requireNonNull( ((IDynamicLuaObject) object).getMethodNames(), "Methods cannot be null" )
+ : LuaMethod.EMPTY_METHODS;
+
LuaTable table = new LuaTable();
- String[] methods = object.getMethodNames();
- for( int i = 0; i < methods.length; i++ )
+ for( int i = 0; i < dynamicMethods.length; i++ )
{
- if( methods[i] != null )
- {
- final int method = i;
- final ILuaObject apiObject = object;
- final String methodName = methods[i];
- table.rawset( methodName, new VarArgFunction()
- {
- @Override
- public Varargs invoke( final LuaState state, Varargs args ) throws LuaError
- {
- Object[] arguments = toObjects( args, 1 );
- Object[] results;
- try
- {
- results = apiObject.callMethod( context, method, arguments );
- }
- catch( InterruptedException e )
- {
- throw new InterruptedError( e );
- }
- catch( LuaException e )
- {
- throw new LuaError( e.getMessage(), e.getLevel() );
- }
- catch( Throwable t )
- {
- if( ComputerCraft.logPeripheralErrors )
- {
- ComputerCraft.log.error( "Error calling " + methodName + " on " + apiObject, t );
- }
- throw new LuaError( "Java Exception Thrown: " + t, 0 );
- }
- return toValues( results );
- }
- } );
- }
+ String method = dynamicMethods[i];
+ table.rawset( method, new ResultInterpreterFunction( this, LuaMethod.DYNAMIC.get( i ), object, context, method ) );
}
+
+ ObjectSource.allMethods( LuaMethod.GENERATOR, object, ( instance, method ) ->
+ table.rawset( method.getName(), method.nonYielding()
+ ? new BasicFunction( this, method.getMethod(), instance, context, method.getName() )
+ : new ResultInterpreterFunction( this, method.getMethod(), instance, context, method.getName() ) ) );
+
+ try
+ {
+ if( table.keyCount() == 0 ) return null;
+ }
+ catch( LuaError ignored )
+ {
+ }
+
return table;
}
@Nonnull
- private LuaValue toValue( @Nullable Object object, @Nonnull Map values )
+ private LuaValue toValue( @Nullable Object object, @Nullable Map values )
{
if( object == null ) return Constants.NIL;
if( object instanceof Number ) return valueOf( ((Number) object).doubleValue() );
@@ -274,13 +263,22 @@ private LuaValue toValue( @Nullable Object object, @Nonnull Map( 1 );
LuaValue result = values.get( object );
if( result != null ) return result;
- if( object instanceof ILuaObject )
+ if( object instanceof IDynamicLuaObject )
{
- LuaValue wrapped = wrapLuaObject( (ILuaObject) object );
+ LuaValue wrapped = wrapLuaObject( object );
+ if( wrapped == null ) wrapped = new LuaTable();
values.put( object, wrapped );
return wrapped;
}
@@ -318,6 +316,13 @@ private LuaValue toValue( @Nullable Object object, @Nonnull Map result = new IdentityHashMap<>( 0 );
LuaValue[] values = new LuaValue[objects.length];
@@ -339,7 +345,7 @@ private Varargs toValues( Object[] objects )
return varargsOf( values );
}
- private static Object toObject( LuaValue value, Map objects )
+ static Object toObject( LuaValue value, Map objects )
{
switch( value.type() )
{
@@ -359,11 +365,12 @@ private static Object toObject( LuaValue value, Map objects )
// Start remembering stuff
if( objects == null )
{
- objects = new IdentityHashMap<>();
+ objects = new IdentityHashMap<>( 1 );
}
- else if( objects.containsKey( value ) )
+ else
{
- return objects.get( value );
+ Object existing = objects.get( value );
+ if( existing != null ) return existing;
}
Map table = new HashMap<>();
objects.put( value, table );
@@ -384,10 +391,7 @@ else if( objects.containsKey( value ) )
break;
}
k = keyValue.first();
- if( k.isNil() )
- {
- break;
- }
+ if( k.isNil() ) break;
LuaValue v = keyValue.arg( 2 );
Object keyObject = toObject( k, objects );
@@ -404,19 +408,19 @@ else if( objects.containsKey( value ) )
}
}
- private static Object[] toObjects( Varargs values, int startIdx )
+ static Object[] toObjects( Varargs values )
{
int count = values.count();
- Object[] objects = new Object[count - startIdx + 1];
- for( int n = startIdx; n <= count; n++ )
- {
- int i = n - startIdx;
- LuaValue value = values.arg( n );
- objects[i] = toObject( value, null );
- }
+ Object[] objects = new Object[count];
+ for( int i = 0; i < count; i++ ) objects[i] = toObject( values.arg( i + 1 ), null );
return objects;
}
+ static IArguments toArguments( Varargs values )
+ {
+ return values == Constants.NONE ? VarargArguments.EMPTY : new VarargArguments( values );
+ }
+
/**
* A {@link DebugHandler} which observes the {@link TimeoutState} and responds accordingly.
*/
@@ -500,23 +504,6 @@ private void handleSoftAbort() throws LuaError
private class CobaltLuaContext implements ILuaContext
{
- @Nonnull
- @Override
- public Object[] yield( Object[] yieldArgs ) throws InterruptedException
- {
- try
- {
- LuaState state = m_state;
- if( state == null ) throw new InterruptedException();
- Varargs results = LuaThread.yieldBlocking( state, toValues( yieldArgs ) );
- return toObjects( results, 1 );
- }
- catch( LuaError e )
- {
- throw new IllegalStateException( e.getMessage() );
- }
- }
-
@Override
public long issueMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException
{
@@ -560,45 +547,6 @@ public long issueMainThreadTask( @Nonnull final ILuaTask task ) throws LuaExcept
throw new LuaException( "Task limit exceeded" );
}
}
-
- @Override
- public Object[] executeMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException, InterruptedException
- {
- // Issue task
- final long taskID = issueMainThreadTask( task );
-
- // Wait for response
- while( true )
- {
- Object[] response = pullEvent( "task_complete" );
- if( response.length >= 3 && response[1] instanceof Number && response[2] instanceof Boolean )
- {
- if( ((Number) response[1]).intValue() == taskID )
- {
- Object[] returnValues = new Object[response.length - 3];
- if( (Boolean) response[2] )
- {
- // Extract the return values from the event and return them
- System.arraycopy( response, 3, returnValues, 0, returnValues.length );
- return returnValues;
- }
- else
- {
- // Extract the error message from the event and raise it
- if( response.length >= 4 && response[3] instanceof String )
- {
- throw new LuaException( (String) response[3] );
- }
- else
- {
- throw new LuaException();
- }
- }
- }
- }
- }
-
- }
}
private static final class HardAbortError extends Error
diff --git a/src/main/java/dan200/computercraft/core/lua/ILuaMachine.java b/src/main/java/dan200/computercraft/core/lua/ILuaMachine.java
index 4ca5d4b07..5c9de9cb3 100644
--- a/src/main/java/dan200/computercraft/core/lua/ILuaMachine.java
+++ b/src/main/java/dan200/computercraft/core/lua/ILuaMachine.java
@@ -5,8 +5,8 @@
*/
package dan200.computercraft.core.lua;
+import dan200.computercraft.api.lua.IDynamicLuaObject;
import dan200.computercraft.api.lua.ILuaAPI;
-import dan200.computercraft.api.lua.ILuaObject;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -21,13 +21,13 @@
* mechanism for registering these.
*
* This should provide implementations of {@link dan200.computercraft.api.lua.ILuaContext}, and the ability to convert
- * {@link ILuaObject}s into something the VM understands, as well as handling method calls.
+ * {@link IDynamicLuaObject}s into something the VM understands, as well as handling method calls.
*/
public interface ILuaMachine
{
/**
* Inject an API into the global environment of this machine. This should construct an object, as it would for any
- * {@link ILuaObject} and set it to all names in {@link ILuaAPI#getNames()}.
+ * {@link IDynamicLuaObject} and set it to all names in {@link ILuaAPI#getNames()}.
*
* Called before {@link #loadBios(InputStream)}.
*
diff --git a/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java b/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java
new file mode 100644
index 000000000..09e6ecd0b
--- /dev/null
+++ b/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java
@@ -0,0 +1,121 @@
+/*
+ * This file is part of ComputerCraft - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
+ * Send enquiries to dratcliffe@gmail.com
+ */
+
+package dan200.computercraft.core.lua;
+
+import dan200.computercraft.ComputerCraft;
+import dan200.computercraft.api.lua.*;
+import dan200.computercraft.core.asm.LuaMethod;
+import org.squiddev.cobalt.*;
+import org.squiddev.cobalt.debug.DebugFrame;
+import org.squiddev.cobalt.function.ResumableVarArgFunction;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Calls a {@link LuaMethod}, and interprets the resulting {@link MethodResult}, either returning the result or yielding
+ * and resuming the supplied continuation.
+ */
+class ResultInterpreterFunction extends ResumableVarArgFunction
+{
+ @Nonnull
+ static class Container
+ {
+ ILuaCallback callback;
+ int errorAdjust;
+
+ Container( ILuaCallback callback, int errorAdjust )
+ {
+ this.callback = callback;
+ this.errorAdjust = errorAdjust;
+ }
+ }
+
+ private final CobaltLuaMachine machine;
+ private final LuaMethod method;
+ private final Object instance;
+ private final ILuaContext context;
+ private final String name;
+
+ ResultInterpreterFunction( CobaltLuaMachine machine, LuaMethod method, Object instance, ILuaContext context, String name )
+ {
+ this.machine = machine;
+ this.method = method;
+ this.instance = instance;
+ this.context = context;
+ this.name = name;
+ }
+
+ @Override
+ protected Varargs invoke( LuaState state, DebugFrame debugFrame, Varargs args ) throws LuaError, UnwindThrowable
+ {
+ IArguments arguments = CobaltLuaMachine.toArguments( args );
+ MethodResult results;
+ try
+ {
+ results = method.apply( instance, context, arguments );
+ }
+ catch( LuaException e )
+ {
+ throw wrap( e, 0 );
+ }
+ catch( Throwable t )
+ {
+ if( ComputerCraft.logPeripheralErrors )
+ {
+ ComputerCraft.log.error( "Error calling " + name + " on " + instance, t );
+ }
+ throw new LuaError( "Java Exception Thrown: " + t, 0 );
+ }
+
+ ILuaCallback callback = results.getCallback();
+ Varargs ret = machine.toValues( results.getResult() );
+
+ if( callback == null ) return ret;
+
+ debugFrame.state = new Container( callback, results.getErrorAdjust() );
+ return LuaThread.yield( state, ret );
+ }
+
+ @Override
+ protected Varargs resumeThis( LuaState state, Container container, Varargs args ) throws LuaError, UnwindThrowable
+ {
+ MethodResult results;
+ Object[] arguments = CobaltLuaMachine.toObjects( args );
+ try
+ {
+ results = container.callback.resume( arguments );
+ }
+ catch( LuaException e )
+ {
+ throw wrap( e, container.errorAdjust );
+ }
+ catch( Throwable t )
+ {
+ if( ComputerCraft.logPeripheralErrors )
+ {
+ ComputerCraft.log.error( "Error calling " + name + " on " + container.callback, t );
+ }
+ throw new LuaError( "Java Exception Thrown: " + t, 0 );
+ }
+
+ Varargs ret = machine.toValues( results.getResult() );
+
+ ILuaCallback callback = results.getCallback();
+ if( callback == null ) return ret;
+
+ container.callback = callback;
+ return LuaThread.yield( state, ret );
+ }
+
+ public static LuaError wrap( LuaException exception, int adjust )
+ {
+ if( !exception.hasLevel() && adjust == 0 ) return new LuaError( exception.getMessage() );
+
+ int level = exception.getLevel();
+ return new LuaError( exception.getMessage(), level <= 0 ? level : level + adjust + 1 );
+ }
+}
diff --git a/src/main/java/dan200/computercraft/core/lua/VarargArguments.java b/src/main/java/dan200/computercraft/core/lua/VarargArguments.java
new file mode 100644
index 000000000..4ad8d58a4
--- /dev/null
+++ b/src/main/java/dan200/computercraft/core/lua/VarargArguments.java
@@ -0,0 +1,102 @@
+/*
+ * This file is part of ComputerCraft - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
+ * Send enquiries to dratcliffe@gmail.com
+ */
+
+package dan200.computercraft.core.lua;
+
+import dan200.computercraft.api.lua.IArguments;
+import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.LuaValues;
+import org.squiddev.cobalt.*;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.nio.ByteBuffer;
+import java.util.Optional;
+
+class VarargArguments implements IArguments
+{
+ static final IArguments EMPTY = new VarargArguments( Constants.NONE );
+
+ private final Varargs varargs;
+ private Object[] cache;
+
+ VarargArguments( Varargs varargs )
+ {
+ this.varargs = varargs;
+ }
+
+ @Override
+ public int count()
+ {
+ return varargs.count();
+ }
+
+ @Nullable
+ @Override
+ public Object get( int index )
+ {
+ if( index < 0 || index >= varargs.count() ) return null;
+
+ Object[] cache = this.cache;
+ if( cache == null )
+ {
+ cache = this.cache = new Object[varargs.count()];
+ }
+ else
+ {
+ Object existing = cache[index];
+ if( existing != null ) return existing;
+ }
+
+ return cache[index] = CobaltLuaMachine.toObject( varargs.arg( index + 1 ), null );
+ }
+
+ @Override
+ public IArguments drop( int count )
+ {
+ if( count < 0 ) throw new IllegalStateException( "count cannot be negative" );
+ if( count == 0 ) return this;
+ return new VarargArguments( varargs.subargs( count + 1 ) );
+ }
+
+ @Override
+ public double getDouble( int index ) throws LuaException
+ {
+ LuaValue value = varargs.arg( index + 1 );
+ if( !(value instanceof LuaNumber) ) throw LuaValues.badArgument( index, "number", value.typeName() );
+ return value.toDouble();
+ }
+
+ @Override
+ public long getLong( int index ) throws LuaException
+ {
+ LuaValue value = varargs.arg( index + 1 );
+ if( !(value instanceof LuaNumber) ) throw LuaValues.badArgument( index, "number", value.typeName() );
+ return value instanceof LuaInteger ? value.toInteger() : (long) LuaValues.checkFinite( index, value.toDouble() );
+ }
+
+ @Nonnull
+ @Override
+ public ByteBuffer getBytes( int index ) throws LuaException
+ {
+ LuaValue value = varargs.arg( index + 1 );
+ if( !(value instanceof LuaString) ) throw LuaValues.badArgument( index, "string", value.typeName() );
+
+ LuaString str = (LuaString) value;
+ return ByteBuffer.wrap( str.bytes, str.offset, str.length ).asReadOnlyBuffer();
+ }
+
+ @Override
+ public Optional optBytes( int index ) throws LuaException
+ {
+ LuaValue value = varargs.arg( index + 1 );
+ if( value.isNil() ) return Optional.empty();
+ if( !(value instanceof LuaString) ) throw LuaValues.badArgument( index, "string", value.typeName() );
+
+ LuaString str = (LuaString) value;
+ return Optional.of( ByteBuffer.wrap( str.bytes, str.offset, str.length ).asReadOnlyBuffer() );
+ }
+}
diff --git a/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java b/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java
index b9c3e7b65..c7ef8986a 100644
--- a/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java
+++ b/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java
@@ -9,9 +9,7 @@
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import dan200.computercraft.ComputerCraft;
-import dan200.computercraft.api.lua.ILuaAPI;
-import dan200.computercraft.api.lua.ILuaContext;
-import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.*;
import dan200.computercraft.shared.computer.blocks.TileCommandComputer;
import dan200.computercraft.shared.util.NBTUtil;
import net.minecraft.block.Block;
@@ -26,43 +24,23 @@
import net.minecraft.world.World;
import net.minecraftforge.registries.ForgeRegistries;
-import javax.annotation.Nonnull;
import java.util.*;
-import static dan200.computercraft.api.lua.ArgumentHelper.getInt;
-import static dan200.computercraft.api.lua.ArgumentHelper.getString;
-
public class CommandAPI implements ILuaAPI
{
- private TileCommandComputer m_computer;
+ private final TileCommandComputer computer;
public CommandAPI( TileCommandComputer computer )
{
- m_computer = computer;
+ this.computer = computer;
}
- // ILuaAPI implementation
-
@Override
public String[] getNames()
{
return new String[] { "commands" };
}
- @Nonnull
- @Override
- public String[] getMethodNames()
- {
- return new String[] {
- "exec",
- "execAsync",
- "list",
- "getBlockPosition",
- "getBlockInfos",
- "getBlockInfo",
- };
- }
-
private static Object createOutput( String output )
{
return new Object[] { output };
@@ -70,18 +48,18 @@ private static Object createOutput( String output )
private Object[] doCommand( String command )
{
- MinecraftServer server = m_computer.getWorld().getServer();
+ MinecraftServer server = computer.getWorld().getServer();
if( server == null || !server.isCommandBlockEnabled() )
{
return new Object[] { false, createOutput( "Command blocks disabled by server" ) };
}
Commands commandManager = server.getCommandManager();
- TileCommandComputer.CommandReceiver receiver = m_computer.getReceiver();
+ TileCommandComputer.CommandReceiver receiver = computer.getReceiver();
try
{
receiver.clearOutput();
- int result = commandManager.handleCommand( m_computer.getSource(), command );
+ int result = commandManager.handleCommand( computer.getSource(), command );
return new Object[] { result > 0, receiver.copyOutput(), result };
}
catch( Throwable t )
@@ -91,7 +69,7 @@ private Object[] doCommand( String command )
}
}
- private static Object getBlockInfo( World world, BlockPos pos )
+ private static Map, ?> getBlockInfo( World world, BlockPos pos )
{
// Get the details of the block
BlockState state = world.getBlockState( pos );
@@ -121,121 +99,100 @@ private static Object getPropertyValue( IProperty property, Comparable value )
return property.getName( value );
}
- @Override
- public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
+ @LuaFunction( mainThread = true )
+ public final Object[] exec( String command )
{
- switch( method )
+ return doCommand( command );
+ }
+
+ @LuaFunction
+ public final long execAsync( ILuaContext context, String command ) throws LuaException
+ {
+ return context.issueMainThreadTask( () -> doCommand( command ) );
+ }
+
+ @LuaFunction( mainThread = true )
+ public final List list( IArguments args ) throws LuaException
+ {
+ MinecraftServer server = computer.getWorld().getServer();
+
+ if( server == null ) return Collections.emptyList();
+ CommandNode node = server.getCommandManager().getDispatcher().getRoot();
+ for( int j = 0; j < args.count(); j++ )
{
- case 0: // exec
+ String name = args.getString( j );
+ node = node.getChild( name );
+ if( !(node instanceof LiteralCommandNode) ) return Collections.emptyList();
+ }
+
+ List result = new ArrayList<>();
+ for( CommandNode> child : node.getChildren() )
+ {
+ if( child instanceof LiteralCommandNode> ) result.add( child.getName() );
+ }
+ return result;
+ }
+
+ @LuaFunction
+ public final Object[] getBlockPosition()
+ {
+ // This is probably safe to do on the Lua thread. Probably.
+ BlockPos pos = computer.getPos();
+ return new Object[] { pos.getX(), pos.getY(), pos.getZ() };
+ }
+
+ @LuaFunction( mainThread = true )
+ public final List> getBlockInfos( int minX, int minY, int minZ, int maxX, int maxY, int maxZ ) throws LuaException
+ {
+ // Get the details of the block
+ World world = computer.getWorld();
+ BlockPos min = new BlockPos(
+ Math.min( minX, maxX ),
+ Math.min( minY, maxY ),
+ Math.min( minZ, maxZ )
+ );
+ BlockPos max = new BlockPos(
+ Math.max( minX, maxX ),
+ Math.max( minY, maxY ),
+ Math.max( minZ, maxZ )
+ );
+ if( !World.isValid( min ) || !World.isValid( max ) )
+ {
+ throw new LuaException( "Co-ordinates out of range" );
+ }
+
+ int blocks = (max.getX() - min.getX() + 1) * (max.getY() - min.getY() + 1) * (max.getZ() - min.getZ() + 1);
+ if( blocks > 4096 ) throw new LuaException( "Too many blocks" );
+
+ List> results = new ArrayList<>( blocks );
+ for( int y = min.getY(); y <= max.getY(); y++ )
+ {
+ for( int z = min.getZ(); z <= max.getZ(); z++ )
{
- final String command = getString( arguments, 0 );
- return context.executeMainThreadTask( () -> doCommand( command ) );
- }
- case 1: // execAsync
- {
- final String command = getString( arguments, 0 );
- long taskID = context.issueMainThreadTask( () -> doCommand( command ) );
- return new Object[] { taskID };
- }
- case 2:
- // list
- return context.executeMainThreadTask( () ->
+ for( int x = min.getX(); x <= max.getX(); x++ )
{
- MinecraftServer server = m_computer.getWorld().getServer();
-
- if( server == null ) return new Object[] { Collections.emptyMap() };
- CommandNode node = server.getCommandManager().getDispatcher().getRoot();
- for( int j = 0; j < arguments.length; j++ )
- {
- String name = getString( arguments, j );
- node = node.getChild( name );
- if( !(node instanceof LiteralCommandNode) ) return new Object[] { Collections.emptyMap() };
- }
-
- List result = new ArrayList<>();
- for( CommandNode> child : node.getChildren() )
- {
- if( child instanceof LiteralCommandNode> ) result.add( child.getName() );
- }
- return new Object[] { result };
- } );
- case 3: // getBlockPosition
- {
- // This is probably safe to do on the Lua thread. Probably.
- BlockPos pos = m_computer.getPos();
- return new Object[] { pos.getX(), pos.getY(), pos.getZ() };
+ BlockPos pos = new BlockPos( x, y, z );
+ results.add( getBlockInfo( world, pos ) );
+ }
}
- case 4:
- {
- // getBlockInfos
- final int minX = getInt( arguments, 0 );
- final int minY = getInt( arguments, 1 );
- final int minZ = getInt( arguments, 2 );
- final int maxX = getInt( arguments, 3 );
- final int maxY = getInt( arguments, 4 );
- final int maxZ = getInt( arguments, 5 );
- return context.executeMainThreadTask( () ->
- {
- // Get the details of the block
- World world = m_computer.getWorld();
- BlockPos min = new BlockPos(
- Math.min( minX, maxX ),
- Math.min( minY, maxY ),
- Math.min( minZ, maxZ )
- );
- BlockPos max = new BlockPos(
- Math.max( minX, maxX ),
- Math.max( minY, maxY ),
- Math.max( minZ, maxZ )
- );
- if( !World.isValid( min ) || !World.isValid( max ) )
- {
- throw new LuaException( "Co-ordinates out of range" );
- }
+ }
- int blocks = (max.getX() - min.getX() + 1) * (max.getY() - min.getY() + 1) * (max.getZ() - min.getZ() + 1);
- if( blocks > 4096 ) throw new LuaException( "Too many blocks" );
+ return results;
+ }
- List results = new ArrayList<>( blocks );
- for( int y = min.getY(); y <= max.getY(); y++ )
- {
- for( int z = min.getZ(); z <= max.getZ(); z++ )
- {
- for( int x = min.getX(); x <= max.getX(); x++ )
- {
- BlockPos pos = new BlockPos( x, y, z );
- results.add( getBlockInfo( world, pos ) );
- }
- }
- }
- return new Object[] { results };
- } );
- }
- case 5:
- {
- // getBlockInfo
- final int x = getInt( arguments, 0 );
- final int y = getInt( arguments, 1 );
- final int z = getInt( arguments, 2 );
- return context.executeMainThreadTask( () ->
- {
- // Get the details of the block
- World world = m_computer.getWorld();
- BlockPos position = new BlockPos( x, y, z );
- if( World.isValid( position ) )
- {
- return new Object[] { getBlockInfo( world, position ) };
- }
- else
- {
- throw new LuaException( "Co-ordinates out of range" );
- }
- } );
- }
- default:
- {
- return null;
- }
+ @LuaFunction( mainThread = true )
+ public final Map, ?> getBlockInfo( int x, int y, int z ) throws LuaException
+ {
+ // Get the details of the block
+ World world = computer.getWorld();
+ BlockPos position = new BlockPos( x, y, z );
+ if( World.isValid( position ) )
+ {
+ return getBlockInfo( world, position );
+ }
+ else
+ {
+ throw new LuaException( "Co-ordinates out of range" );
}
}
}
diff --git a/src/main/java/dan200/computercraft/shared/computer/blocks/ComputerPeripheral.java b/src/main/java/dan200/computercraft/shared/computer/blocks/ComputerPeripheral.java
index a536a9e29..71b394256 100644
--- a/src/main/java/dan200/computercraft/shared/computer/blocks/ComputerPeripheral.java
+++ b/src/main/java/dan200/computercraft/shared/computer/blocks/ComputerPeripheral.java
@@ -5,69 +5,63 @@
*/
package dan200.computercraft.shared.computer.blocks;
-import dan200.computercraft.api.lua.ILuaContext;
-import dan200.computercraft.api.peripheral.IComputerAccess;
+import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.IPeripheral;
import javax.annotation.Nonnull;
public class ComputerPeripheral implements IPeripheral
{
- private final String m_type;
- private final ComputerProxy m_computer;
+ private final String type;
+ private final ComputerProxy computer;
public ComputerPeripheral( String type, ComputerProxy computer )
{
- m_type = type;
- m_computer = computer;
+ this.type = type;
+ this.computer = computer;
}
- // IPeripheral implementation
-
@Nonnull
@Override
public String getType()
{
- return m_type;
+ return type;
}
- @Nonnull
- @Override
- public String[] getMethodNames()
+ @LuaFunction
+ public final void turnOn()
{
- return new String[] {
- "turnOn",
- "shutdown",
- "reboot",
- "getID",
- "isOn",
- "getLabel",
- };
+ computer.turnOn();
}
- @Override
- public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments )
+ @LuaFunction
+ public final void shutdown()
{
- switch( method )
- {
- case 0: // turnOn
- m_computer.turnOn();
- return null;
- case 1: // shutdown
- m_computer.shutdown();
- return null;
- case 2: // reboot
- m_computer.reboot();
- return null;
- case 3: // getID
- return new Object[] { m_computer.assignID() };
- case 4: // isOn
- return new Object[] { m_computer.isOn() };
- case 5: // getLabel
- return new Object[] { m_computer.getLabel() };
- default:
- return null;
- }
+ computer.shutdown();
+ }
+
+ @LuaFunction
+ public final void reboot()
+ {
+ computer.reboot();
+ }
+
+ @LuaFunction
+ public final int getID()
+ {
+ return computer.assignID();
+ }
+
+ @LuaFunction
+ public final boolean isOn()
+ {
+ return computer.isOn();
+ }
+
+ @LuaFunction
+ public final String getLabel()
+ {
+ return computer.getLabel();
}
@Override
@@ -80,6 +74,6 @@ public boolean equals( IPeripheral other )
@Override
public Object getTarget()
{
- return m_computer.getTile();
+ return computer.getTile();
}
}
diff --git a/src/main/java/dan200/computercraft/shared/peripheral/commandblock/CommandBlockPeripheral.java b/src/main/java/dan200/computercraft/shared/peripheral/commandblock/CommandBlockPeripheral.java
index 2ed7dd1ea..383f65f62 100644
--- a/src/main/java/dan200/computercraft/shared/peripheral/commandblock/CommandBlockPeripheral.java
+++ b/src/main/java/dan200/computercraft/shared/peripheral/commandblock/CommandBlockPeripheral.java
@@ -5,27 +5,21 @@
*/
package dan200.computercraft.shared.peripheral.commandblock;
-import dan200.computercraft.api.lua.ILuaContext;
-import dan200.computercraft.api.lua.LuaException;
-import dan200.computercraft.api.peripheral.IComputerAccess;
+import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.IPeripheral;
import net.minecraft.tileentity.CommandBlockTileEntity;
import javax.annotation.Nonnull;
-import static dan200.computercraft.api.lua.ArgumentHelper.getString;
-
public class CommandBlockPeripheral implements IPeripheral
{
- private final CommandBlockTileEntity m_commandBlock;
+ private final CommandBlockTileEntity commandBlock;
public CommandBlockPeripheral( CommandBlockTileEntity commandBlock )
{
- m_commandBlock = commandBlock;
+ this.commandBlock = commandBlock;
}
- // IPeripheral methods
-
@Nonnull
@Override
public String getType()
@@ -33,54 +27,25 @@ public String getType()
return "command";
}
- @Nonnull
- @Override
- public String[] getMethodNames()
+ @LuaFunction( mainThread = true )
+ public final String getCommand()
{
- return new String[] {
- "getCommand",
- "setCommand",
- "runCommand",
- };
+ return commandBlock.getCommandBlockLogic().getCommand();
}
- @Override
- public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull final Object[] arguments ) throws LuaException, InterruptedException
+ @LuaFunction( mainThread = true )
+ public final void setCommand( String command )
{
- switch( method )
- {
- case 0: // getCommand
- return context.executeMainThreadTask( () -> new Object[] {
- m_commandBlock.getCommandBlockLogic().getCommand(),
- } );
- case 1:
- {
- // setCommand
- final String command = getString( arguments, 0 );
- context.issueMainThreadTask( () ->
- {
- m_commandBlock.getCommandBlockLogic().setCommand( command );
- m_commandBlock.getCommandBlockLogic().updateCommand();
- return null;
- } );
- return null;
- }
- case 2: // runCommand
- return context.executeMainThreadTask( () ->
- {
- m_commandBlock.getCommandBlockLogic().trigger( m_commandBlock.getWorld() );
- int result = m_commandBlock.getCommandBlockLogic().getSuccessCount();
- if( result > 0 )
- {
- return new Object[] { true };
- }
- else
- {
- return new Object[] { false, "Command failed" };
- }
- } );
- }
- return null;
+ commandBlock.getCommandBlockLogic().setCommand( command );
+ commandBlock.getCommandBlockLogic().updateCommand();
+ }
+
+ @LuaFunction( mainThread = true )
+ public final Object runCommand()
+ {
+ commandBlock.getCommandBlockLogic().trigger( commandBlock.getWorld() );
+ int result = commandBlock.getCommandBlockLogic().getSuccessCount();
+ return result > 0 ? new Object[] { true } : new Object[] { false, "Command failed" };
}
@Override
diff --git a/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/DiskDrivePeripheral.java b/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/DiskDrivePeripheral.java
index 97d7ef2cf..1a56cd236 100644
--- a/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/DiskDrivePeripheral.java
+++ b/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/DiskDrivePeripheral.java
@@ -5,8 +5,8 @@
*/
package dan200.computercraft.shared.peripheral.diskdrive;
-import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
@@ -16,16 +16,15 @@
import net.minecraft.item.ItemStack;
import javax.annotation.Nonnull;
+import java.util.Optional;
-import static dan200.computercraft.api.lua.ArgumentHelper.optString;
-
-class DiskDrivePeripheral implements IPeripheral
+public class DiskDrivePeripheral implements IPeripheral
{
- private final TileDiskDrive m_diskDrive;
+ private final TileDiskDrive diskDrive;
DiskDrivePeripheral( TileDiskDrive diskDrive )
{
- m_diskDrive = diskDrive;
+ this.diskDrive = diskDrive;
}
@Nonnull
@@ -35,114 +34,110 @@ public String getType()
return "drive";
}
- @Nonnull
- @Override
- public String[] getMethodNames()
+ @LuaFunction
+ public final boolean isDiskPresent()
{
- return new String[] {
- "isDiskPresent",
- "getDiskLabel",
- "setDiskLabel",
- "hasData",
- "getMountPath",
- "hasAudio",
- "getAudioTitle",
- "playAudio",
- "stopAudio",
- "ejectDisk",
- "getDiskID",
- };
+ return !diskDrive.getDiskStack().isEmpty();
}
- @Override
- public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
+ @LuaFunction
+ public final Object[] getDiskLabel()
{
- switch( method )
+ ItemStack stack = diskDrive.getDiskStack();
+ IMedia media = MediaProviders.get( stack );
+ return media == null ? null : new Object[] { media.getLabel( stack ) };
+ }
+
+ @LuaFunction( mainThread = true )
+ public final void setDiskLabel( Optional labelA ) throws LuaException
+ {
+ String label = labelA.orElse( null );
+ ItemStack stack = diskDrive.getDiskStack();
+ IMedia media = MediaProviders.get( stack );
+ if( media == null ) return;
+
+ if( !media.setLabel( stack, StringUtil.normaliseLabel( label ) ) )
{
- case 0: // isDiskPresent
- return new Object[] { !m_diskDrive.getDiskStack().isEmpty() };
- case 1: // getDiskLabel
- {
- ItemStack stack = m_diskDrive.getDiskStack();
- IMedia media = MediaProviders.get( stack );
- return media == null ? null : new Object[] { media.getLabel( stack ) };
- }
- case 2: // setDiskLabel
- {
- String label = optString( arguments, 0, null );
-
- return context.executeMainThreadTask( () -> {
- ItemStack stack = m_diskDrive.getDiskStack();
- IMedia media = MediaProviders.get( stack );
- if( media == null ) return null;
-
- if( !media.setLabel( stack, StringUtil.normaliseLabel( label ) ) )
- {
- throw new LuaException( "Disk label cannot be changed" );
- }
- m_diskDrive.setDiskStack( stack );
- return null;
- } );
- }
- case 3: // hasData
- return new Object[] { m_diskDrive.getDiskMountPath( computer ) != null };
- case 4: // getMountPath
- return new Object[] { m_diskDrive.getDiskMountPath( computer ) };
- case 5:
- {
- // hasAudio
- ItemStack stack = m_diskDrive.getDiskStack();
- IMedia media = MediaProviders.get( stack );
- return new Object[] { media != null && media.getAudio( stack ) != null };
- }
- case 6:
- {
- // getAudioTitle
- ItemStack stack = m_diskDrive.getDiskStack();
- IMedia media = MediaProviders.get( stack );
- return new Object[] { media != null ? media.getAudioTitle( stack ) : false };
- }
- case 7: // playAudio
- m_diskDrive.playDiskAudio();
- return null;
- case 8: // stopAudio
- m_diskDrive.stopDiskAudio();
- return null;
- case 9: // eject
- m_diskDrive.ejectDisk();
- return null;
- case 10: // getDiskID
- {
- ItemStack disk = m_diskDrive.getDiskStack();
- return disk.getItem() instanceof ItemDisk ? new Object[] { ItemDisk.getDiskID( disk ) } : null;
- }
- default:
- return null;
+ throw new LuaException( "Disk label cannot be changed" );
}
+ diskDrive.setDiskStack( stack );
+ }
+
+ @LuaFunction
+ public final boolean hasData( IComputerAccess computer )
+ {
+ return diskDrive.getDiskMountPath( computer ) != null;
+ }
+
+ @LuaFunction
+ public final String getMountPath( IComputerAccess computer )
+ {
+ return diskDrive.getDiskMountPath( computer );
+ }
+
+ @LuaFunction
+ public final boolean hasAudio()
+ {
+ ItemStack stack = diskDrive.getDiskStack();
+ IMedia media = MediaProviders.get( stack );
+ return media != null && media.getAudio( stack ) != null;
+ }
+
+ @LuaFunction
+ public final Object getAudioTitle()
+ {
+ ItemStack stack = diskDrive.getDiskStack();
+ IMedia media = MediaProviders.get( stack );
+ return media != null ? media.getAudioTitle( stack ) : false;
+ }
+
+ @LuaFunction
+ public final void playAudio()
+ {
+ diskDrive.playDiskAudio();
+ }
+
+ @LuaFunction
+ public final void stopAudio()
+ {
+ diskDrive.stopDiskAudio();
+ }
+
+ @LuaFunction
+ public final void eject()
+ {
+ diskDrive.ejectDisk();
+ }
+
+ @LuaFunction
+ public final Object[] getDiskID()
+ {
+ ItemStack disk = diskDrive.getDiskStack();
+ return disk.getItem() instanceof ItemDisk ? new Object[] { ItemDisk.getDiskID( disk ) } : null;
}
@Override
public void attach( @Nonnull IComputerAccess computer )
{
- m_diskDrive.mount( computer );
+ diskDrive.mount( computer );
}
@Override
public void detach( @Nonnull IComputerAccess computer )
{
- m_diskDrive.unmount( computer );
+ diskDrive.unmount( computer );
}
@Override
public boolean equals( IPeripheral other )
{
- return this == other || other instanceof DiskDrivePeripheral && ((DiskDrivePeripheral) other).m_diskDrive == m_diskDrive;
+ return this == other || other instanceof DiskDrivePeripheral && ((DiskDrivePeripheral) other).diskDrive == diskDrive;
}
@Nonnull
@Override
public Object getTarget()
{
- return m_diskDrive;
+ return diskDrive;
}
}
diff --git a/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/TileDiskDrive.java b/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/TileDiskDrive.java
index 79a8422a4..a6337d1dc 100644
--- a/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/TileDiskDrive.java
+++ b/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/TileDiskDrive.java
@@ -434,7 +434,7 @@ private synchronized void mountDisk( IComputerAccess computer )
info.mountPath = null;
}
}
- computer.queueEvent( "disk", new Object[] { computer.getAttachmentName() } );
+ computer.queueEvent( "disk", computer.getAttachmentName() );
}
}
@@ -449,7 +449,7 @@ private synchronized void unmountDisk( IComputerAccess computer )
computer.unmount( info.mountPath );
info.mountPath = null;
}
- computer.queueEvent( "disk_eject", new Object[] { computer.getAttachmentName() } );
+ computer.queueEvent( "disk_eject", computer.getAttachmentName() );
}
}
diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/ModemPeripheral.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/ModemPeripheral.java
index 7a29744a7..32df37b98 100644
--- a/src/main/java/dan200/computercraft/shared/peripheral/modem/ModemPeripheral.java
+++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/ModemPeripheral.java
@@ -5,8 +5,8 @@
*/
package dan200.computercraft.shared.peripheral.modem;
-import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.network.IPacketNetwork;
import dan200.computercraft.api.network.IPacketReceiver;
import dan200.computercraft.api.network.IPacketSender;
@@ -20,8 +20,6 @@
import java.util.HashSet;
import java.util.Set;
-import static dan200.computercraft.api.lua.ArgumentHelper.getInt;
-
public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPacketReceiver
{
private IPacketNetwork m_network;
@@ -52,11 +50,6 @@ private synchronized void setNetwork( IPacketNetwork network )
if( m_network != null ) m_network.addReceiver( this );
}
- protected void switchNetwork()
- {
- setNetwork( getNetwork() );
- }
-
public void destroy()
{
setNetwork( null );
@@ -71,9 +64,8 @@ public void receiveSameDimension( @Nonnull Packet packet, double distance )
{
for( IComputerAccess computer : m_computers )
{
- computer.queueEvent( "modem_message", new Object[] {
- computer.getAttachmentName(), packet.getChannel(), packet.getReplyChannel(), packet.getPayload(), distance,
- } );
+ computer.queueEvent( "modem_message",
+ computer.getAttachmentName(), packet.getChannel(), packet.getReplyChannel(), packet.getPayload(), distance );
}
}
}
@@ -87,17 +79,14 @@ public void receiveDifferentDimension( @Nonnull Packet packet )
{
for( IComputerAccess computer : m_computers )
{
- computer.queueEvent( "modem_message", new Object[] {
- computer.getAttachmentName(), packet.getChannel(), packet.getReplyChannel(), packet.getPayload(),
- } );
+ computer.queueEvent( "modem_message",
+ computer.getAttachmentName(), packet.getChannel(), packet.getReplyChannel(), packet.getPayload() );
}
}
}
protected abstract IPacketNetwork getNetwork();
- // IPeripheral implementation
-
@Nonnull
@Override
public String getType()
@@ -105,90 +94,64 @@ public String getType()
return "modem";
}
- @Nonnull
- @Override
- public String[] getMethodNames()
+ private static int parseChannel( int channel ) throws LuaException
{
- return new String[] {
- "open",
- "isOpen",
- "close",
- "closeAll",
- "transmit",
- "isWireless",
- };
- }
-
- private static int parseChannel( Object[] arguments, int index ) throws LuaException
- {
- int channel = getInt( arguments, index );
- if( channel < 0 || channel > 65535 )
- {
- throw new LuaException( "Expected number in range 0-65535" );
- }
+ if( channel < 0 || channel > 65535 ) throw new LuaException( "Expected number in range 0-65535" );
return channel;
}
- @Override
- public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
+ @LuaFunction
+ public final void open( int channel ) throws LuaException
{
- switch( method )
+ m_state.open( parseChannel( channel ) );
+ }
+
+ @LuaFunction
+ public final boolean isOpen( int channel ) throws LuaException
+ {
+ return m_state.isOpen( parseChannel( channel ) );
+ }
+
+ @LuaFunction
+ public final void close( int channel ) throws LuaException
+ {
+ m_state.close( parseChannel( channel ) );
+ }
+
+ @LuaFunction
+ public final void closeAll()
+ {
+ m_state.closeAll();
+ }
+
+ @LuaFunction
+ public final void transmit( int channel, int replyChannel, Object payload ) throws LuaException
+ {
+ parseChannel( channel );
+ parseChannel( replyChannel );
+
+ World world = getWorld();
+ Vec3d position = getPosition();
+ IPacketNetwork network = m_network;
+
+ if( world == null || position == null || network == null ) return;
+
+ Packet packet = new Packet( channel, replyChannel, payload, this );
+ if( isInterdimensional() )
{
- case 0:
- {
- // open
- int channel = parseChannel( arguments, 0 );
- m_state.open( channel );
- return null;
- }
- case 1:
- {
- // isOpen
- int channel = parseChannel( arguments, 0 );
- return new Object[] { m_state.isOpen( channel ) };
- }
- case 2:
- {
- // close
- int channel = parseChannel( arguments, 0 );
- m_state.close( channel );
- return null;
- }
- case 3: // closeAll
- m_state.closeAll();
- return null;
- case 4:
- {
- // transmit
- int channel = parseChannel( arguments, 0 );
- int replyChannel = parseChannel( arguments, 1 );
- Object payload = arguments.length > 2 ? arguments[2] : null;
- World world = getWorld();
- Vec3d position = getPosition();
- IPacketNetwork network = m_network;
- if( world != null && position != null && network != null )
- {
- Packet packet = new Packet( channel, replyChannel, payload, this );
- if( isInterdimensional() )
- {
- network.transmitInterdimensional( packet );
- }
- else
- {
- network.transmitSameDimension( packet, getRange() );
- }
- }
- return null;
- }
- case 5:
- {
- // isWireless
- IPacketNetwork network = m_network;
- return new Object[] { network != null && network.isWireless() };
- }
- default:
- return null;
+ network.transmitInterdimensional( packet );
}
+ else
+ {
+ network.transmitSameDimension( packet, getRange() );
+ }
+ }
+
+ @LuaFunction
+ public final boolean isWireless()
+ {
+ IPacketNetwork network = m_network;
+ return network != null && network.isWireless();
}
@Override
diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemPeripheral.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemPeripheral.java
index 88223b71d..620f02cbd 100644
--- a/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemPeripheral.java
+++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemPeripheral.java
@@ -8,27 +8,28 @@
import com.google.common.collect.ImmutableMap;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
-import dan200.computercraft.api.lua.ILuaContext;
-import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.network.IPacketNetwork;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.network.wired.IWiredSender;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IWorkMonitor;
+import dan200.computercraft.core.apis.PeripheralAPI;
+import dan200.computercraft.core.asm.PeripheralMethod;
import dan200.computercraft.shared.peripheral.modem.ModemPeripheral;
import dan200.computercraft.shared.peripheral.modem.ModemState;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
-import static dan200.computercraft.api.lua.ArgumentHelper.getString;
-
public abstract class WiredModemPeripheral extends ModemPeripheral implements IWiredSender
{
private final WiredModemElement modem;
@@ -71,93 +72,51 @@ public World getWorld()
protected abstract WiredModemLocalPeripheral getLocalPeripheral();
//endregion
- //region IPeripheral
- @Nonnull
- @Override
- public String[] getMethodNames()
+ //region Peripheral methods
+ @LuaFunction
+ public final Collection getNamesRemote( IComputerAccess computer )
{
- String[] methods = super.getMethodNames();
- String[] newMethods = new String[methods.length + 6];
- System.arraycopy( methods, 0, newMethods, 0, methods.length );
- newMethods[methods.length] = "getNamesRemote";
- newMethods[methods.length + 1] = "isPresentRemote";
- newMethods[methods.length + 2] = "getTypeRemote";
- newMethods[methods.length + 3] = "getMethodsRemote";
- newMethods[methods.length + 4] = "callRemote";
- newMethods[methods.length + 5] = "getNameLocal";
- return newMethods;
+ return getWrappers( computer ).keySet();
}
- @Override
- public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
+ @LuaFunction
+ public final boolean isPresentRemote( IComputerAccess computer, String name )
{
- String[] methods = super.getMethodNames();
- switch( method - methods.length )
- {
- case 0:
- {
- // getNamesRemote
- Map wrappers = getWrappers( computer );
- Map table = new HashMap<>();
- if( wrappers != null )
- {
- int idx = 1;
- for( String name : wrappers.keySet() ) table.put( idx++, name );
- }
- return new Object[] { table };
- }
- case 1:
- {
- // isPresentRemote
- String name = getString( arguments, 0 );
- return new Object[] { getWrapper( computer, name ) != null };
- }
- case 2:
- {
- // getTypeRemote
- String name = getString( arguments, 0 );
- RemotePeripheralWrapper wrapper = getWrapper( computer, name );
- return wrapper != null ? new Object[] { wrapper.getType() } : null;
- }
- case 3:
- {
- // getMethodsRemote
- String name = getString( arguments, 0 );
- RemotePeripheralWrapper wrapper = getWrapper( computer, name );
- if( wrapper == null ) return null;
+ return getWrapper( computer, name ) != null;
+ }
- String[] methodNames = wrapper.getMethodNames();
- Map table = new HashMap<>();
- for( int i = 0; i < methodNames.length; i++ )
- {
- table.put( i + 1, methodNames[i] );
- }
- return new Object[] { table };
- }
- case 4:
- {
- // callRemote
- String remoteName = getString( arguments, 0 );
- String methodName = getString( arguments, 1 );
- RemotePeripheralWrapper wrapper = getWrapper( computer, remoteName );
- if( wrapper == null ) throw new LuaException( "No peripheral: " + remoteName );
+ @LuaFunction
+ public final Object[] getTypeRemote( IComputerAccess computer, String name )
+ {
+ RemotePeripheralWrapper wrapper = getWrapper( computer, name );
+ return wrapper != null ? new Object[] { wrapper.getType() } : null;
+ }
- Object[] methodArgs = new Object[arguments.length - 2];
- System.arraycopy( arguments, 2, methodArgs, 0, arguments.length - 2 );
- return wrapper.callMethod( context, methodName, methodArgs );
- }
- case 5:
- {
- // getNameLocal
- String local = getLocalPeripheral().getConnectedName();
- return local == null ? null : new Object[] { local };
- }
- default:
- {
- // The regular modem methods
- return super.callMethod( computer, context, method, arguments );
- }
- }
+ @LuaFunction
+ public final Object[] getMethodsRemote( IComputerAccess computer, String name )
+ {
+ RemotePeripheralWrapper wrapper = getWrapper( computer, name );
+ if( wrapper == null ) return null;
+
+ return new Object[] { wrapper.getMethodNames() };
+ }
+
+ @LuaFunction
+ public final MethodResult callRemote( IComputerAccess computer, ILuaContext context, IArguments arguments ) throws LuaException
+ {
+ String remoteName = arguments.getString( 0 );
+ String methodName = arguments.getString( 1 );
+ RemotePeripheralWrapper wrapper = getWrapper( computer, remoteName );
+ if( wrapper == null ) throw new LuaException( "No peripheral: " + remoteName );
+
+ return wrapper.callMethod( context, methodName, arguments.drop( 2 ) );
+ }
+
+ @LuaFunction
+ public final Object[] getNameLocal()
+ {
+ String local = getLocalPeripheral().getConnectedName();
+ return local == null ? null : new Object[] { local };
}
@Override
@@ -267,67 +226,52 @@ private RemotePeripheralWrapper getWrapper( IComputerAccess computer, String rem
private static class RemotePeripheralWrapper implements IComputerAccess
{
- private final WiredModemElement m_element;
- private final IPeripheral m_peripheral;
- private final IComputerAccess m_computer;
- private final String m_name;
+ private final WiredModemElement element;
+ private final IPeripheral peripheral;
+ private final IComputerAccess computer;
+ private final String name;
- private final String m_type;
- private final String[] m_methods;
- private final Map m_methodMap;
+ private final String type;
+ private final Map methodMap;
RemotePeripheralWrapper( WiredModemElement element, IPeripheral peripheral, IComputerAccess computer, String name )
{
- m_element = element;
- m_peripheral = peripheral;
- m_computer = computer;
- m_name = name;
+ this.element = element;
+ this.peripheral = peripheral;
+ this.computer = computer;
+ this.name = name;
- m_type = peripheral.getType();
- m_methods = peripheral.getMethodNames();
- assert m_type != null;
- assert m_methods != null;
-
- m_methodMap = new HashMap<>();
- for( int i = 0; i < m_methods.length; i++ )
- {
- if( m_methods[i] != null )
- {
- m_methodMap.put( m_methods[i], i );
- }
- }
+ type = Objects.requireNonNull( peripheral.getType(), "Peripheral type cannot be null" );
+ methodMap = PeripheralAPI.getMethods( peripheral );
}
public void attach()
{
- m_peripheral.attach( this );
- m_computer.queueEvent( "peripheral", new Object[] { getAttachmentName() } );
+ peripheral.attach( this );
+ computer.queueEvent( "peripheral", getAttachmentName() );
}
public void detach()
{
- m_peripheral.detach( this );
- m_computer.queueEvent( "peripheral_detach", new Object[] { getAttachmentName() } );
+ peripheral.detach( this );
+ computer.queueEvent( "peripheral_detach", getAttachmentName() );
}
public String getType()
{
- return m_type;
+ return type;
}
- public String[] getMethodNames()
+ public Collection getMethodNames()
{
- return m_methods;
+ return methodMap.keySet();
}
- public Object[] callMethod( ILuaContext context, String methodName, Object[] arguments ) throws LuaException, InterruptedException
+ public MethodResult callMethod( ILuaContext context, String methodName, IArguments arguments ) throws LuaException
{
- if( m_methodMap.containsKey( methodName ) )
- {
- int method = m_methodMap.get( methodName );
- return m_peripheral.callMethod( this, context, method, arguments );
- }
- throw new LuaException( "No such method " + methodName );
+ PeripheralMethod method = methodMap.get( methodName );
+ if( method == null ) throw new LuaException( "No such method " + methodName );
+ return method.apply( peripheral, context, this, arguments );
}
// IComputerAccess implementation
@@ -335,66 +279,66 @@ public Object[] callMethod( ILuaContext context, String methodName, Object[] arg
@Override
public String mount( @Nonnull String desiredLocation, @Nonnull IMount mount )
{
- return m_computer.mount( desiredLocation, mount, m_name );
+ return computer.mount( desiredLocation, mount, name );
}
@Override
public String mount( @Nonnull String desiredLocation, @Nonnull IMount mount, @Nonnull String driveName )
{
- return m_computer.mount( desiredLocation, mount, driveName );
+ return computer.mount( desiredLocation, mount, driveName );
}
@Override
public String mountWritable( @Nonnull String desiredLocation, @Nonnull IWritableMount mount )
{
- return m_computer.mountWritable( desiredLocation, mount, m_name );
+ return computer.mountWritable( desiredLocation, mount, name );
}
@Override
public String mountWritable( @Nonnull String desiredLocation, @Nonnull IWritableMount mount, @Nonnull String driveName )
{
- return m_computer.mountWritable( desiredLocation, mount, driveName );
+ return computer.mountWritable( desiredLocation, mount, driveName );
}
@Override
public void unmount( String location )
{
- m_computer.unmount( location );
+ computer.unmount( location );
}
@Override
public int getID()
{
- return m_computer.getID();
+ return computer.getID();
}
@Override
- public void queueEvent( @Nonnull String event, Object[] arguments )
+ public void queueEvent( @Nonnull String event, Object... arguments )
{
- m_computer.queueEvent( event, arguments );
+ computer.queueEvent( event, arguments );
}
@Nonnull
@Override
public IWorkMonitor getMainThreadMonitor()
{
- return m_computer.getMainThreadMonitor();
+ return computer.getMainThreadMonitor();
}
@Nonnull
@Override
public String getAttachmentName()
{
- return m_name;
+ return name;
}
@Nonnull
@Override
public Map getAvailablePeripherals()
{
- synchronized( m_element.getRemotePeripherals() )
+ synchronized( element.getRemotePeripherals() )
{
- return ImmutableMap.copyOf( m_element.getRemotePeripherals() );
+ return ImmutableMap.copyOf( element.getRemotePeripherals() );
}
}
@@ -402,9 +346,9 @@ public Map getAvailablePeripherals()
@Override
public IPeripheral getAvailablePeripheral( @Nonnull String name )
{
- synchronized( m_element.getRemotePeripherals() )
+ synchronized( element.getRemotePeripherals() )
{
- return m_element.getRemotePeripherals().get( name );
+ return element.getRemotePeripherals().get( name );
}
}
}
diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/WirelessModemPeripheral.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/WirelessModemPeripheral.java
index a6db98161..1bd677a8c 100644
--- a/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/WirelessModemPeripheral.java
+++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/WirelessModemPeripheral.java
@@ -14,7 +14,7 @@
public abstract class WirelessModemPeripheral extends ModemPeripheral
{
- private boolean m_advanced;
+ private final boolean m_advanced;
public WirelessModemPeripheral( ModemState state, boolean advanced )
{
diff --git a/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorPeripheral.java b/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorPeripheral.java
index 17246c424..67c88dc40 100644
--- a/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorPeripheral.java
+++ b/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorPeripheral.java
@@ -5,26 +5,23 @@
*/
package dan200.computercraft.shared.peripheral.monitor;
-import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.LuaFunction;
+import dan200.computercraft.api.lua.LuaValues;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
-import dan200.computercraft.core.apis.TermAPI;
+import dan200.computercraft.core.apis.TermMethods;
import dan200.computercraft.core.terminal.Terminal;
-import dan200.computercraft.shared.util.Palette;
-import org.apache.commons.lang3.ArrayUtils;
import javax.annotation.Nonnull;
-import static dan200.computercraft.api.lua.ArgumentHelper.*;
-
-public class MonitorPeripheral implements IPeripheral
+public class MonitorPeripheral extends TermMethods implements IPeripheral
{
- private final TileMonitor m_monitor;
+ private final TileMonitor monitor;
public MonitorPeripheral( TileMonitor monitor )
{
- m_monitor = monitor;
+ this.monitor = monitor;
}
@Nonnull
@@ -34,201 +31,58 @@ public String getType()
return "monitor";
}
- @Nonnull
- @Override
- public String[] getMethodNames()
+ @LuaFunction
+ public final void setTextScale( double scaleArg ) throws LuaException
{
- return new String[] {
- "write",
- "scroll",
- "setCursorPos",
- "setCursorBlink",
- "getCursorPos",
- "getSize",
- "clear",
- "clearLine",
- "setTextScale",
- "setTextColour",
- "setTextColor",
- "setBackgroundColour",
- "setBackgroundColor",
- "isColour",
- "isColor",
- "getTextColour",
- "getTextColor",
- "getBackgroundColour",
- "getBackgroundColor",
- "blit",
- "setPaletteColour",
- "setPaletteColor",
- "getPaletteColour",
- "getPaletteColor",
- "getTextScale",
- "getCursorBlink",
- };
+ int scale = (int) (LuaValues.checkFinite( 0, scaleArg ) * 2.0);
+ if( scale < 1 || scale > 10 ) throw new LuaException( "Expected number in range 0.5-5" );
+ getMonitor().setTextScale( scale );
}
- @Override
- public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
+ @LuaFunction
+ public final double getTextScale() throws LuaException
{
- ServerMonitor monitor = m_monitor.getCachedServerMonitor();
- if( monitor == null ) throw new LuaException( "Monitor has been detached" );
-
- Terminal terminal = monitor.getTerminal();
- if( terminal == null ) throw new LuaException( "Monitor has been detached" );
-
- switch( method )
- {
- case 0:
- {
- // write
- String text = args.length > 0 && args[0] != null ? args[0].toString() : "";
- terminal.write( text );
- terminal.setCursorPos( terminal.getCursorX() + text.length(), terminal.getCursorY() );
- return null;
- }
- case 1:
- {
- // scroll
- int value = getInt( args, 0 );
- terminal.scroll( value );
- return null;
- }
- case 2:
- {
- // setCursorPos
- int x = getInt( args, 0 ) - 1;
- int y = getInt( args, 1 ) - 1;
- terminal.setCursorPos( x, y );
- return null;
- }
- case 3:
- {
- // setCursorBlink
- boolean blink = getBoolean( args, 0 );
- terminal.setCursorBlink( blink );
- return null;
- }
- case 4: // getCursorPos
- return new Object[] { terminal.getCursorX() + 1, terminal.getCursorY() + 1 };
- case 5: // getSize
- return new Object[] { terminal.getWidth(), terminal.getHeight() };
- case 6: // clear
- terminal.clear();
- return null;
- case 7: // clearLine
- terminal.clearLine();
- return null;
- case 8:
- {
- // setTextScale
- int scale = (int) (getFiniteDouble( args, 0 ) * 2.0);
- if( scale < 1 || scale > 10 )
- {
- throw new LuaException( "Expected number in range 0.5-5" );
- }
- monitor.setTextScale( scale );
- return null;
- }
- case 9:
- case 10:
- {
- // setTextColour/setTextColor
- int colour = TermAPI.parseColour( args );
- terminal.setTextColour( colour );
- return null;
- }
- case 11:
- case 12:
- {
- // setBackgroundColour/setBackgroundColor
- int colour = TermAPI.parseColour( args );
- terminal.setBackgroundColour( colour );
- return null;
- }
- case 13:
- case 14: // isColour/isColor
- return new Object[] { monitor.isColour() };
- case 15:
- case 16: // getTextColour/getTextColor
- return TermAPI.encodeColour( terminal.getTextColour() );
- case 17:
- case 18: // getBackgroundColour/getBackgroundColor
- return TermAPI.encodeColour( terminal.getBackgroundColour() );
- case 19:
- {
- // blit
- String text = getString( args, 0 );
- String textColour = getString( args, 1 );
- String backgroundColour = getString( args, 2 );
- if( textColour.length() != text.length() || backgroundColour.length() != text.length() )
- {
- throw new LuaException( "Arguments must be the same length" );
- }
-
- terminal.blit( text, textColour, backgroundColour );
- terminal.setCursorPos( terminal.getCursorX() + text.length(), terminal.getCursorY() );
- return null;
- }
- case 20:
- case 21:
- {
- // setPaletteColour/setPaletteColor
- int colour = 15 - TermAPI.parseColour( args );
- if( args.length == 2 )
- {
- int hex = getInt( args, 1 );
- double[] rgb = Palette.decodeRGB8( hex );
- TermAPI.setColour( terminal, colour, rgb[0], rgb[1], rgb[2] );
- }
- else
- {
- double r = getFiniteDouble( args, 1 );
- double g = getFiniteDouble( args, 2 );
- double b = getFiniteDouble( args, 3 );
- TermAPI.setColour( terminal, colour, r, g, b );
- }
- return null;
- }
- case 22:
- case 23:
- {
- // getPaletteColour/getPaletteColor
- Palette palette = terminal.getPalette();
-
- int colour = 15 - TermAPI.parseColour( args );
-
- if( palette != null )
- {
- return ArrayUtils.toObject( palette.getColour( colour ) );
- }
- return null;
- }
- case 24: // getTextScale
- return new Object[] { monitor.getTextScale() / 2.0 };
- case 25:
- // getCursorBlink
- return new Object[] { terminal.getCursorBlink() };
- default:
- return null;
- }
+ return getMonitor().getTextScale() / 2.0;
}
@Override
public void attach( @Nonnull IComputerAccess computer )
{
- m_monitor.addComputer( computer );
+ monitor.addComputer( computer );
}
@Override
public void detach( @Nonnull IComputerAccess computer )
{
- m_monitor.removeComputer( computer );
+ monitor.removeComputer( computer );
}
@Override
public boolean equals( IPeripheral other )
{
- return other instanceof MonitorPeripheral && m_monitor == ((MonitorPeripheral) other).m_monitor;
+ return other instanceof MonitorPeripheral && monitor == ((MonitorPeripheral) other).monitor;
+ }
+
+ @Nonnull
+ private ServerMonitor getMonitor() throws LuaException
+ {
+ ServerMonitor monitor = this.monitor.getCachedServerMonitor();
+ if( monitor == null ) throw new LuaException( "Monitor has been detached" );
+ return monitor;
+ }
+
+ @Nonnull
+ @Override
+ public Terminal getTerminal() throws LuaException
+ {
+ Terminal terminal = getMonitor().getTerminal();
+ if( terminal == null ) throw new LuaException( "Monitor has been detached" );
+ return terminal;
+ }
+
+ @Override
+ public boolean isColour() throws LuaException
+ {
+ return getMonitor().isColour();
}
}
diff --git a/src/main/java/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java b/src/main/java/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java
index fa692be17..ff185fffc 100644
--- a/src/main/java/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java
+++ b/src/main/java/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java
@@ -165,9 +165,7 @@ public void blockTick()
for( IComputerAccess computer : monitor.m_computers )
{
- computer.queueEvent( "monitor_resize", new Object[] {
- computer.getAttachmentName(),
- } );
+ computer.queueEvent( "monitor_resize", computer.getAttachmentName() );
}
}
}
@@ -635,9 +633,7 @@ private void monitorTouched( float xPos, float yPos, float zPos )
for( IComputerAccess computer : monitor.m_computers )
{
- computer.queueEvent( "monitor_touch", new Object[] {
- computer.getAttachmentName(), xCharPos, yCharPos,
- } );
+ computer.queueEvent( "monitor_touch", computer.getAttachmentName(), xCharPos, yCharPos );
}
}
}
diff --git a/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterPeripheral.java b/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterPeripheral.java
index b699b8eb5..ee83959d4 100644
--- a/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterPeripheral.java
+++ b/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterPeripheral.java
@@ -5,25 +5,22 @@
*/
package dan200.computercraft.shared.peripheral.printer;
-import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
-import dan200.computercraft.api.peripheral.IComputerAccess;
+import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.util.StringUtil;
import javax.annotation.Nonnull;
-
-import static dan200.computercraft.api.lua.ArgumentHelper.getInt;
-import static dan200.computercraft.api.lua.ArgumentHelper.optString;
+import java.util.Optional;
public class PrinterPeripheral implements IPeripheral
{
- private final TilePrinter m_printer;
+ private final TilePrinter printer;
public PrinterPeripheral( TilePrinter printer )
{
- m_printer = printer;
+ this.printer = printer;
}
@Nonnull
@@ -33,108 +30,94 @@ public String getType()
return "printer";
}
- @Nonnull
- @Override
- public String[] getMethodNames()
+ // FIXME: There's a theoretical race condition here between getCurrentPage and then using the page. Ideally
+ // we'd lock on the page, consume it, and unlock.
+
+ // FIXME: None of our page modification functions actually mark the tile as dirty, so the page may not be
+ // persisted correctly.
+
+ public final void write( Object[] args ) throws LuaException
{
- return new String[] {
- "write",
- "setCursorPos",
- "getCursorPos",
- "getPageSize",
- "newPage",
- "endPage",
- "getInkLevel",
- "setPageTitle",
- "getPaperLevel",
- };
+ String text = args.length > 0 && args[0] != null ? args[0].toString() : "";
+ Terminal page = getCurrentPage();
+ page.write( text );
+ page.setCursorPos( page.getCursorX() + text.length(), page.getCursorY() );
}
- @Override
- public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException, InterruptedException
+ @LuaFunction
+ public final Object[] getCursorPos() throws LuaException
{
- // FIXME: There's a theoretical race condition here between getCurrentPage and then using the page. Ideally
- // we'd lock on the page, consume it, and unlock.
+ Terminal page = getCurrentPage();
+ int x = page.getCursorX();
+ int y = page.getCursorY();
+ return new Object[] { x + 1, y + 1 };
+ }
- // FIXME: None of our page modification functions actually mark the tile as dirty, so the page may not be
- // persisted correctly.
- switch( method )
- {
- case 0: // write
- {
- String text = args.length > 0 && args[0] != null ? args[0].toString() : "";
- Terminal page = getCurrentPage();
- page.write( text );
- page.setCursorPos( page.getCursorX() + text.length(), page.getCursorY() );
- return null;
- }
- case 1:
- {
- // setCursorPos
- int x = getInt( args, 0 ) - 1;
- int y = getInt( args, 1 ) - 1;
- Terminal page = getCurrentPage();
- page.setCursorPos( x, y );
- return null;
- }
- case 2:
- {
- // getCursorPos
- Terminal page = getCurrentPage();
- int x = page.getCursorX();
- int y = page.getCursorY();
- return new Object[] { x + 1, y + 1 };
- }
- case 3:
- {
- // getPageSize
- Terminal page = getCurrentPage();
- int width = page.getWidth();
- int height = page.getHeight();
- return new Object[] { width, height };
- }
- case 4: // newPage
- return context.executeMainThreadTask( () -> new Object[] { m_printer.startNewPage() } );
- case 5: // endPage
- getCurrentPage();
- return context.executeMainThreadTask( () -> {
- getCurrentPage();
- return new Object[] { m_printer.endCurrentPage() };
- } );
- case 6: // getInkLevel
- return new Object[] { m_printer.getInkLevel() };
- case 7:
- {
- // setPageTitle
- String title = optString( args, 0, "" );
- getCurrentPage();
- m_printer.setPageTitle( StringUtil.normaliseLabel( title ) );
- return null;
- }
- case 8: // getPaperLevel
- return new Object[] { m_printer.getPaperLevel() };
- default:
- return null;
- }
+ @LuaFunction
+ public final void setCursorPos( int x, int y ) throws LuaException
+ {
+ Terminal page = getCurrentPage();
+ page.setCursorPos( x - 1, y - 1 );
+ }
+
+ @LuaFunction
+ public final Object[] getPageSize() throws LuaException
+ {
+ Terminal page = getCurrentPage();
+ int width = page.getWidth();
+ int height = page.getHeight();
+ return new Object[] { width, height };
+ }
+
+ @LuaFunction( mainThread = true )
+ public final boolean newPage()
+ {
+ return printer.startNewPage();
+ }
+
+ @LuaFunction( mainThread = true )
+ public final boolean endPage() throws LuaException
+ {
+ getCurrentPage();
+ return printer.endCurrentPage();
+ }
+
+ @LuaFunction
+ public final void setPageTitle( Optional title ) throws LuaException
+ {
+ getCurrentPage();
+ printer.setPageTitle( StringUtil.normaliseLabel( title.orElse( "" ) ) );
+ }
+
+ @LuaFunction
+ public final int getInkLevel()
+ {
+ return printer.getInkLevel();
+ }
+
+ @LuaFunction
+ public final int getPaperLevel()
+ {
+ return printer.getPaperLevel();
}
@Override
public boolean equals( IPeripheral other )
{
- return other instanceof PrinterPeripheral && ((PrinterPeripheral) other).m_printer == m_printer;
+ return other instanceof PrinterPeripheral && ((PrinterPeripheral) other).printer == printer;
}
@Nonnull
@Override
public Object getTarget()
{
- return m_printer;
+ return printer;
}
@Nonnull
private Terminal getCurrentPage() throws LuaException
{
- Terminal currentPage = m_printer.getCurrentPage();
+ Terminal currentPage = printer.getCurrentPage();
if( currentPage == null ) throw new LuaException( "Page not started" );
return currentPage;
}
diff --git a/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java b/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java
index 0352c8163..4920e7260 100644
--- a/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java
+++ b/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java
@@ -8,7 +8,7 @@
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
-import dan200.computercraft.api.peripheral.IComputerAccess;
+import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.IPeripheral;
import net.minecraft.network.play.server.SPlaySoundPacket;
import net.minecraft.server.MinecraftServer;
@@ -20,10 +20,10 @@
import net.minecraft.world.World;
import javax.annotation.Nonnull;
+import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
-import static dan200.computercraft.api.lua.ArgumentHelper.getString;
-import static dan200.computercraft.api.lua.ArgumentHelper.optFiniteDouble;
+import static dan200.computercraft.api.lua.LuaValues.checkFinite;
public abstract class SpeakerPeripheral implements IPeripheral
{
@@ -53,54 +53,30 @@ public String getType()
return "speaker";
}
- @Nonnull
- @Override
- public String[] getMethodNames()
+ @LuaFunction
+ public final boolean playSound( ILuaContext context, String name, Optional volumeA, Optional pitchA ) throws LuaException
{
- return new String[] {
- "playSound",
- "playNote",
- };
- }
+ float volume = (float) checkFinite( 1, volumeA.orElse( 1.0 ) );
+ float pitch = (float) checkFinite( 2, pitchA.orElse( 1.0 ) );
- @Override
- public Object[] callMethod( @Nonnull IComputerAccess computerAccess, @Nonnull ILuaContext context, int methodIndex, @Nonnull Object[] args ) throws LuaException
- {
- switch( methodIndex )
+ ResourceLocation identifier;
+ try
{
- case 0: // playSound
- {
- String name = getString( args, 0 );
- float volume = (float) optFiniteDouble( args, 1, 1.0 );
- float pitch = (float) optFiniteDouble( args, 2, 1.0 );
-
- ResourceLocation identifier;
- try
- {
- identifier = new ResourceLocation( name );
- }
- catch( ResourceLocationException e )
- {
- throw new LuaException( "Malformed sound name '" + name + "' " );
- }
-
- return new Object[] { playSound( context, identifier, volume, pitch, false ) };
- }
-
- case 1: // playNote
- return playNote( args, context );
-
- default:
- throw new IllegalStateException( "Method index out of range!" );
+ identifier = new ResourceLocation( name );
}
+ catch( ResourceLocationException e )
+ {
+ throw new LuaException( "Malformed sound name '" + name + "' " );
+ }
+
+ return playSound( context, identifier, volume, pitch, false );
}
- @Nonnull
- private synchronized Object[] playNote( Object[] arguments, ILuaContext context ) throws LuaException
+ @LuaFunction
+ public final synchronized boolean playNote( ILuaContext context, String name, Optional volumeA, Optional pitchA ) throws LuaException
{
- String name = getString( arguments, 0 );
- float volume = (float) optFiniteDouble( arguments, 1, 1.0 );
- float pitch = (float) optFiniteDouble( arguments, 2, 1.0 );
+ float volume = (float) checkFinite( 1, volumeA.orElse( 1.0 ) );
+ float pitch = (float) checkFinite( 2, pitchA.orElse( 1.0 ) );
NoteBlockInstrument instrument = null;
for( NoteBlockInstrument testInstrument : NoteBlockInstrument.values() )
@@ -113,16 +89,12 @@ private synchronized Object[] playNote( Object[] arguments, ILuaContext context
}
// Check if the note exists
- if( instrument == null )
- {
- throw new LuaException( "Invalid instrument, \"" + name + "\"!" );
- }
+ if( instrument == null ) throw new LuaException( "Invalid instrument, \"" + name + "\"!" );
// If the resource location for note block notes changes, this method call will need to be updated
boolean success = playSound( context, instrument.getSound().getRegistryName(), volume, (float) Math.pow( 2.0, (pitch - 12.0) / 12.0 ), true );
-
if( success ) m_notesThisTick.incrementAndGet();
- return new Object[] { success };
+ return success;
}
private synchronized boolean playSound( ILuaContext context, ResourceLocation name, float volume, float pitch, boolean isNote ) throws LuaException
diff --git a/src/main/java/dan200/computercraft/shared/pocket/apis/PocketAPI.java b/src/main/java/dan200/computercraft/shared/pocket/apis/PocketAPI.java
index 9d481758b..15f8df2c0 100644
--- a/src/main/java/dan200/computercraft/shared/pocket/apis/PocketAPI.java
+++ b/src/main/java/dan200/computercraft/shared/pocket/apis/PocketAPI.java
@@ -6,8 +6,7 @@
package dan200.computercraft.shared.pocket.apis;
import dan200.computercraft.api.lua.ILuaAPI;
-import dan200.computercraft.api.lua.ILuaContext;
-import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.shared.PocketUpgrades;
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
@@ -20,15 +19,13 @@
import net.minecraft.util.NonNullList;
import net.minecraftforge.items.wrapper.PlayerMainInvWrapper;
-import javax.annotation.Nonnull;
-
public class PocketAPI implements ILuaAPI
{
- private final PocketServerComputer m_computer;
+ private final PocketServerComputer computer;
public PocketAPI( PocketServerComputer computer )
{
- m_computer = computer;
+ this.computer = computer;
}
@Override
@@ -37,89 +34,68 @@ public String[] getNames()
return new String[] { "pocket" };
}
- @Nonnull
- @Override
- public String[] getMethodNames()
+ @LuaFunction( mainThread = true )
+ public final Object[] equipBack()
{
- return new String[] {
- "equipBack",
- "unequipBack",
- };
+ Entity entity = computer.getEntity();
+ if( !(entity instanceof PlayerEntity) ) return new Object[] { false, "Cannot find player" };
+ PlayerEntity player = (PlayerEntity) entity;
+ PlayerInventory inventory = player.inventory;
+ IPocketUpgrade previousUpgrade = computer.getUpgrade();
+
+ // Attempt to find the upgrade, starting in the main segment, and then looking in the opposite
+ // one. We start from the position the item is currently in and loop round to the start.
+ IPocketUpgrade newUpgrade = findUpgrade( inventory.mainInventory, inventory.currentItem, previousUpgrade );
+ if( newUpgrade == null )
+ {
+ newUpgrade = findUpgrade( inventory.offHandInventory, 0, previousUpgrade );
+ }
+ if( newUpgrade == null ) return new Object[] { false, "Cannot find a valid upgrade" };
+
+ // Remove the current upgrade
+ if( previousUpgrade != null )
+ {
+ ItemStack stack = previousUpgrade.getCraftingItem();
+ if( !stack.isEmpty() )
+ {
+ stack = InventoryUtil.storeItems( stack, new PlayerMainInvWrapper( inventory ), inventory.currentItem );
+ if( !stack.isEmpty() )
+ {
+ WorldUtil.dropItemStack( stack, player.getEntityWorld(), player.getPositionVec() );
+ }
+ }
+ }
+
+ // Set the new upgrade
+ computer.setUpgrade( newUpgrade );
+
+ return new Object[] { true };
}
- @Override
- public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
+ @LuaFunction( mainThread = true )
+ public final Object[] unequipBack()
{
- switch( method )
+ Entity entity = computer.getEntity();
+ if( !(entity instanceof PlayerEntity) ) return new Object[] { false, "Cannot find player" };
+ PlayerEntity player = (PlayerEntity) entity;
+ PlayerInventory inventory = player.inventory;
+ IPocketUpgrade previousUpgrade = computer.getUpgrade();
+
+ if( previousUpgrade == null ) return new Object[] { false, "Nothing to unequip" };
+
+ computer.setUpgrade( null );
+
+ ItemStack stack = previousUpgrade.getCraftingItem();
+ if( !stack.isEmpty() )
{
- case 0:
- // equipBack
- return context.executeMainThreadTask( () ->
- {
- Entity entity = m_computer.getEntity();
- if( !(entity instanceof PlayerEntity) ) return new Object[] { false, "Cannot find player" };
- PlayerEntity player = (PlayerEntity) entity;
- PlayerInventory inventory = player.inventory;
- IPocketUpgrade previousUpgrade = m_computer.getUpgrade();
-
- // Attempt to find the upgrade, starting in the main segment, and then looking in the opposite
- // one. We start from the position the item is currently in and loop round to the start.
- IPocketUpgrade newUpgrade = findUpgrade( inventory.mainInventory, inventory.currentItem, previousUpgrade );
- if( newUpgrade == null )
- {
- newUpgrade = findUpgrade( inventory.offHandInventory, 0, previousUpgrade );
- }
- if( newUpgrade == null ) return new Object[] { false, "Cannot find a valid upgrade" };
-
- // Remove the current upgrade
- if( previousUpgrade != null )
- {
- ItemStack stack = previousUpgrade.getCraftingItem();
- if( !stack.isEmpty() )
- {
- stack = InventoryUtil.storeItems( stack, new PlayerMainInvWrapper( inventory ), inventory.currentItem );
- if( !stack.isEmpty() )
- {
- WorldUtil.dropItemStack( stack, player.getEntityWorld(), player.getPositionVec() );
- }
- }
- }
-
- // Set the new upgrade
- m_computer.setUpgrade( newUpgrade );
-
- return new Object[] { true };
- } );
-
- case 1:
- // unequipBack
- return context.executeMainThreadTask( () ->
- {
- Entity entity = m_computer.getEntity();
- if( !(entity instanceof PlayerEntity) ) return new Object[] { false, "Cannot find player" };
- PlayerEntity player = (PlayerEntity) entity;
- PlayerInventory inventory = player.inventory;
- IPocketUpgrade previousUpgrade = m_computer.getUpgrade();
-
- if( previousUpgrade == null ) return new Object[] { false, "Nothing to unequip" };
-
- m_computer.setUpgrade( null );
-
- ItemStack stack = previousUpgrade.getCraftingItem();
- if( !stack.isEmpty() )
- {
- stack = InventoryUtil.storeItems( stack, new PlayerMainInvWrapper( inventory ), inventory.currentItem );
- if( stack.isEmpty() )
- {
- WorldUtil.dropItemStack( stack, player.getEntityWorld(), player.getPositionVec() );
- }
- }
-
- return new Object[] { true };
- } );
- default:
- return null;
+ stack = InventoryUtil.storeItems( stack, new PlayerMainInvWrapper( inventory ), inventory.currentItem );
+ if( stack.isEmpty() )
+ {
+ WorldUtil.dropItemStack( stack, player.getEntityWorld(), player.getPositionVec() );
+ }
}
+
+ return new Object[] { true };
}
private static IPocketUpgrade findUpgrade( NonNullList inv, int start, IPocketUpgrade previous )
diff --git a/src/main/java/dan200/computercraft/shared/turtle/apis/TurtleAPI.java b/src/main/java/dan200/computercraft/shared/turtle/apis/TurtleAPI.java
index a124d8e76..ddd19fcea 100644
--- a/src/main/java/dan200/computercraft/shared/turtle/apis/TurtleAPI.java
+++ b/src/main/java/dan200/computercraft/shared/turtle/apis/TurtleAPI.java
@@ -5,9 +5,7 @@
*/
package dan200.computercraft.shared.turtle.apis;
-import dan200.computercraft.api.lua.ILuaAPI;
-import dan200.computercraft.api.lua.ILuaContext;
-import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleCommand;
import dan200.computercraft.api.turtle.TurtleCommandResult;
@@ -22,343 +20,333 @@
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.registries.ForgeRegistries;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
-
-import static dan200.computercraft.api.lua.ArgumentHelper.*;
+import java.util.Optional;
public class TurtleAPI implements ILuaAPI
{
- private IAPIEnvironment m_environment;
- private ITurtleAccess m_turtle;
+ private final IAPIEnvironment environment;
+ private final ITurtleAccess turtle;
public TurtleAPI( IAPIEnvironment environment, ITurtleAccess turtle )
{
- m_environment = environment;
- m_turtle = turtle;
+ this.environment = environment;
+ this.turtle = turtle;
}
- // ILuaAPI implementation
-
@Override
public String[] getNames()
{
return new String[] { "turtle" };
}
- @Nonnull
- @Override
- public String[] getMethodNames()
+ private MethodResult trackCommand( ITurtleCommand command )
{
- return new String[] {
- "forward",
- "back",
- "up",
- "down",
- "turnLeft",
- "turnRight",
- "dig",
- "digUp",
- "digDown",
- "place",
- "placeUp",
- "placeDown",
- "drop",
- "select",
- "getItemCount",
- "getItemSpace",
- "detect",
- "detectUp",
- "detectDown",
- "compare",
- "compareUp",
- "compareDown",
- "attack",
- "attackUp",
- "attackDown",
- "dropUp",
- "dropDown",
- "suck",
- "suckUp",
- "suckDown",
- "getFuelLevel",
- "refuel",
- "compareTo",
- "transferTo",
- "getSelectedSlot",
- "getFuelLimit",
- "equipLeft",
- "equipRight",
- "inspect",
- "inspectUp",
- "inspectDown",
- "getItemDetail",
- };
+ environment.addTrackingChange( TrackingField.TURTLE_OPS );
+ return turtle.executeCommand( command );
}
- private Object[] tryCommand( ILuaContext context, ITurtleCommand command ) throws LuaException, InterruptedException
+ @LuaFunction
+ public final MethodResult forward()
{
- return m_turtle.executeCommand( context, command );
+ return trackCommand( new TurtleMoveCommand( MoveDirection.FORWARD ) );
}
- private int parseSlotNumber( Object[] arguments, int index ) throws LuaException
+ @LuaFunction
+ public final MethodResult back()
+ {
+ return trackCommand( new TurtleMoveCommand( MoveDirection.BACK ) );
+ }
+
+ @LuaFunction
+ public final MethodResult up()
+ {
+ return trackCommand( new TurtleMoveCommand( MoveDirection.UP ) );
+ }
+
+ @LuaFunction
+ public final MethodResult down()
+ {
+ return trackCommand( new TurtleMoveCommand( MoveDirection.DOWN ) );
+ }
+
+ @LuaFunction
+ public final MethodResult turnLeft()
+ {
+ return trackCommand( new TurtleTurnCommand( TurnDirection.LEFT ) );
+ }
+
+ @LuaFunction
+ public final MethodResult turnRight()
+ {
+ return trackCommand( new TurtleTurnCommand( TurnDirection.RIGHT ) );
+ }
+
+ @LuaFunction
+ public final MethodResult dig( Optional side )
+ {
+ environment.addTrackingChange( TrackingField.TURTLE_OPS );
+ return trackCommand( TurtleToolCommand.dig( InteractDirection.FORWARD, side.orElse( null ) ) );
+ }
+
+ @LuaFunction
+ public final MethodResult digUp( Optional side )
+ {
+ environment.addTrackingChange( TrackingField.TURTLE_OPS );
+ return trackCommand( TurtleToolCommand.dig( InteractDirection.UP, side.orElse( null ) ) );
+ }
+
+ @LuaFunction
+ public final MethodResult digDown( Optional side )
+ {
+ environment.addTrackingChange( TrackingField.TURTLE_OPS );
+ return trackCommand( TurtleToolCommand.dig( InteractDirection.DOWN, side.orElse( null ) ) );
+ }
+
+ @LuaFunction
+ public final MethodResult place( IArguments args )
+ {
+ return trackCommand( new TurtlePlaceCommand( InteractDirection.FORWARD, args.getAll() ) );
+ }
+
+ @LuaFunction
+ public final MethodResult placeUp( IArguments args )
+ {
+ return trackCommand( new TurtlePlaceCommand( InteractDirection.UP, args.getAll() ) );
+ }
+
+ @LuaFunction
+ public final MethodResult placeDown( IArguments args )
+ {
+ return trackCommand( new TurtlePlaceCommand( InteractDirection.DOWN, args.getAll() ) );
+ }
+
+ @LuaFunction
+ public final MethodResult drop( Optional count ) throws LuaException
+ {
+ return trackCommand( new TurtleDropCommand( InteractDirection.FORWARD, checkCount( count ) ) );
+ }
+
+ @LuaFunction
+ public final MethodResult dropUp( Optional count ) throws LuaException
+ {
+ return trackCommand( new TurtleDropCommand( InteractDirection.UP, checkCount( count ) ) );
+ }
+
+ @LuaFunction
+ public final MethodResult dropDown( Optional count ) throws LuaException
+ {
+ return trackCommand( new TurtleDropCommand( InteractDirection.DOWN, checkCount( count ) ) );
+ }
+
+ @LuaFunction
+ public final MethodResult select( int slot ) throws LuaException
+ {
+ int actualSlot = checkSlot( slot );
+ return turtle.executeCommand( turtle -> {
+ turtle.setSelectedSlot( actualSlot );
+ return TurtleCommandResult.success();
+ } );
+ }
+
+ @LuaFunction
+ public final int getItemCount( Optional slot ) throws LuaException
+ {
+ int actualSlot = checkSlot( slot ).orElse( turtle.getSelectedSlot() );
+ return turtle.getInventory().getStackInSlot( actualSlot ).getCount();
+ }
+
+ @LuaFunction
+ public final int getItemSpace( Optional slot ) throws LuaException
+ {
+ int actualSlot = checkSlot( slot ).orElse( turtle.getSelectedSlot() );
+ ItemStack stack = turtle.getInventory().getStackInSlot( actualSlot );
+ return stack.isEmpty() ? 64 : Math.min( stack.getMaxStackSize(), 64 ) - stack.getCount();
+ }
+
+ @LuaFunction
+ public final MethodResult detect()
+ {
+ return trackCommand( new TurtleDetectCommand( InteractDirection.FORWARD ) );
+ }
+
+ @LuaFunction
+ public final MethodResult detectUp()
+ {
+ return trackCommand( new TurtleDetectCommand( InteractDirection.UP ) );
+ }
+
+ @LuaFunction
+ public final MethodResult detectDown()
+ {
+ return trackCommand( new TurtleDetectCommand( InteractDirection.DOWN ) );
+ }
+
+ @LuaFunction
+ public final MethodResult compare()
+ {
+ return trackCommand( new TurtleCompareCommand( InteractDirection.FORWARD ) );
+ }
+
+ @LuaFunction
+ public final MethodResult compareUp()
+ {
+ return trackCommand( new TurtleCompareCommand( InteractDirection.UP ) );
+ }
+
+ @LuaFunction
+ public final MethodResult compareDown()
+ {
+ return trackCommand( new TurtleCompareCommand( InteractDirection.DOWN ) );
+ }
+
+ @LuaFunction
+ public final MethodResult attack( Optional side )
+ {
+ return trackCommand( TurtleToolCommand.attack( InteractDirection.FORWARD, side.orElse( null ) ) );
+ }
+
+ @LuaFunction
+ public final MethodResult attackUp( Optional side )
+ {
+ return trackCommand( TurtleToolCommand.attack( InteractDirection.UP, side.orElse( null ) ) );
+ }
+
+ @LuaFunction
+ public final MethodResult attackDown( Optional side )
+ {
+ return trackCommand( TurtleToolCommand.attack( InteractDirection.DOWN, side.orElse( null ) ) );
+ }
+
+ @LuaFunction
+ public final MethodResult suck( Optional count ) throws LuaException
+ {
+ return trackCommand( new TurtleSuckCommand( InteractDirection.FORWARD, checkCount( count ) ) );
+ }
+
+ @LuaFunction
+ public final MethodResult suckUp( Optional count ) throws LuaException
+ {
+ return trackCommand( new TurtleSuckCommand( InteractDirection.UP, checkCount( count ) ) );
+ }
+
+ @LuaFunction
+ public final MethodResult suckDown( Optional count ) throws LuaException
+ {
+ return trackCommand( new TurtleSuckCommand( InteractDirection.DOWN, checkCount( count ) ) );
+ }
+
+ @LuaFunction
+ public final Object getFuelLevel()
+ {
+ return turtle.isFuelNeeded() ? turtle.getFuelLevel() : "unlimited";
+ }
+
+ @LuaFunction
+ public final MethodResult refuel( Optional countA ) throws LuaException
+ {
+ int count = countA.orElse( Integer.MAX_VALUE );
+ if( count < 0 ) throw new LuaException( "Refuel count " + count + " out of range" );
+ return trackCommand( new TurtleRefuelCommand( count ) );
+ }
+
+ @LuaFunction
+ public final MethodResult compareTo( int slot ) throws LuaException
+ {
+ return trackCommand( new TurtleCompareToCommand( checkSlot( slot ) ) );
+ }
+
+ @LuaFunction
+ public final MethodResult transferTo( int slotArg, Optional countArg ) throws LuaException
+ {
+ int slot = checkSlot( slotArg );
+ int count = checkCount( countArg );
+ return trackCommand( new TurtleTransferToCommand( slot, count ) );
+ }
+
+ @LuaFunction
+ public final int getSelectedSlot()
+ {
+ return turtle.getSelectedSlot() + 1;
+ }
+
+ @LuaFunction
+ public final Object getFuelLimit()
+ {
+ return turtle.isFuelNeeded() ? turtle.getFuelLimit() : "unlimited";
+ }
+
+ @LuaFunction
+ public final MethodResult equipLeft()
+ {
+ return trackCommand( new TurtleEquipCommand( TurtleSide.LEFT ) );
+ }
+
+ @LuaFunction
+ public final MethodResult equipRight()
+ {
+ return trackCommand( new TurtleEquipCommand( TurtleSide.RIGHT ) );
+ }
+
+ @LuaFunction
+ public final MethodResult inspect()
+ {
+ return trackCommand( new TurtleInspectCommand( InteractDirection.FORWARD ) );
+ }
+
+ @LuaFunction
+ public final MethodResult inspectUp()
+ {
+ return trackCommand( new TurtleInspectCommand( InteractDirection.UP ) );
+ }
+
+ @LuaFunction
+ public final MethodResult inspectDown()
+ {
+ return trackCommand( new TurtleInspectCommand( InteractDirection.DOWN ) );
+ }
+
+ @LuaFunction
+ public final Object[] getItemDetail( Optional slotArg ) throws LuaException
+ {
+ // FIXME: There's a race condition here if the stack is being modified (mutating NBT, etc...)
+ // on another thread. The obvious solution is to move this into a command, but some programs rely
+ // on this having a 0-tick delay.
+ int slot = checkSlot( slotArg ).orElse( turtle.getSelectedSlot() );
+ ItemStack stack = turtle.getInventory().getStackInSlot( slot );
+ if( stack.isEmpty() ) return new Object[] { null };
+
+ Item item = stack.getItem();
+ String name = ForgeRegistries.ITEMS.getKey( item ).toString();
+ int count = stack.getCount();
+
+ Map table = new HashMap<>();
+ table.put( "name", name );
+ table.put( "count", count );
+
+ TurtleActionEvent event = new TurtleInspectItemEvent( turtle, stack, table );
+ if( MinecraftForge.EVENT_BUS.post( event ) ) return new Object[] { false, event.getFailureMessage() };
+
+ return new Object[] { table };
+ }
+
+
+ private static int checkSlot( int slot ) throws LuaException
{
- int slot = getInt( arguments, index );
if( slot < 1 || slot > 16 ) throw new LuaException( "Slot number " + slot + " out of range" );
return slot - 1;
}
- private int parseOptionalSlotNumber( Object[] arguments, int index, int fallback ) throws LuaException
+ private static Optional checkSlot( Optional slot ) throws LuaException
{
- if( index >= arguments.length || arguments[index] == null ) return fallback;
- return parseSlotNumber( arguments, index );
+ return slot.isPresent() ? Optional.of( checkSlot( slot.get() ) ) : Optional.empty();
}
- private static int parseCount( Object[] arguments, int index ) throws LuaException
+ private static int checkCount( Optional countArg ) throws LuaException
{
- int count = optInt( arguments, index, 64 );
+ int count = countArg.orElse( 64 );
if( count < 0 || count > 64 ) throw new LuaException( "Item count " + count + " out of range" );
return count;
}
-
- @Nullable
- private static TurtleSide parseSide( Object[] arguments, int index ) throws LuaException
- {
- String side = optString( arguments, index, null );
- if( side == null )
- {
- return null;
- }
- else if( side.equalsIgnoreCase( "left" ) )
- {
- return TurtleSide.LEFT;
- }
- else if( side.equalsIgnoreCase( "right" ) )
- {
- return TurtleSide.RIGHT;
- }
- else
- {
- throw new LuaException( "Invalid side" );
- }
- }
-
- @Override
- public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException, InterruptedException
- {
- switch( method )
- {
- case 0: // forward
- m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
- return tryCommand( context, new TurtleMoveCommand( MoveDirection.FORWARD ) );
- case 1: // back
- m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
- return tryCommand( context, new TurtleMoveCommand( MoveDirection.BACK ) );
- case 2: // up
- m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
- return tryCommand( context, new TurtleMoveCommand( MoveDirection.UP ) );
- case 3: // down
- m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
- return tryCommand( context, new TurtleMoveCommand( MoveDirection.DOWN ) );
- case 4: // turnLeft
- m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
- return tryCommand( context, new TurtleTurnCommand( TurnDirection.LEFT ) );
- case 5: // turnRight
- m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
- return tryCommand( context, new TurtleTurnCommand( TurnDirection.RIGHT ) );
- case 6:
- {
- // dig
- TurtleSide side = parseSide( args, 0 );
- m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
- return tryCommand( context, TurtleToolCommand.dig( InteractDirection.FORWARD, side ) );
- }
- case 7:
- {
- // digUp
- TurtleSide side = parseSide( args, 0 );
- m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
- return tryCommand( context, TurtleToolCommand.dig( InteractDirection.UP, side ) );
- }
- case 8:
- {
- // digDown
- TurtleSide side = parseSide( args, 0 );
- m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
- return tryCommand( context, TurtleToolCommand.dig( InteractDirection.DOWN, side ) );
- }
- case 9: // place
- m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
- return tryCommand( context, new TurtlePlaceCommand( InteractDirection.FORWARD, args ) );
- case 10: // placeUp
- m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
- return tryCommand( context, new TurtlePlaceCommand( InteractDirection.UP, args ) );
- case 11: // placeDown
- m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
- return tryCommand( context, new TurtlePlaceCommand( InteractDirection.DOWN, args ) );
- case 12:
- {
- // drop
- int count = parseCount( args, 0 );
- m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
- return tryCommand( context, new TurtleDropCommand( InteractDirection.FORWARD, count ) );
- }
- case 13:
- {
- // select
- int slot = parseSlotNumber( args, 0 );
- return tryCommand( context, turtle -> {
- turtle.setSelectedSlot( slot );
- return TurtleCommandResult.success();
- } );
- }
- case 14:
- {
- // getItemCount
- int slot = parseOptionalSlotNumber( args, 0, m_turtle.getSelectedSlot() );
- ItemStack stack = m_turtle.getInventory().getStackInSlot( slot );
- return new Object[] { stack.getCount() };
- }
- case 15:
- {
- // getItemSpace
- int slot = parseOptionalSlotNumber( args, 0, m_turtle.getSelectedSlot() );
- ItemStack stack = m_turtle.getInventory().getStackInSlot( slot );
- return new Object[] { stack.isEmpty() ? 64 : Math.min( stack.getMaxStackSize(), 64 ) - stack.getCount() };
- }
- case 16: // detect
- return tryCommand( context, new TurtleDetectCommand( InteractDirection.FORWARD ) );
- case 17: // detectUp
- return tryCommand( context, new TurtleDetectCommand( InteractDirection.UP ) );
- case 18: // detectDown
- return tryCommand( context, new TurtleDetectCommand( InteractDirection.DOWN ) );
- case 19: // compare
- return tryCommand( context, new TurtleCompareCommand( InteractDirection.FORWARD ) );
- case 20: // compareUp
- return tryCommand( context, new TurtleCompareCommand( InteractDirection.UP ) );
- case 21: // compareDown
- return tryCommand( context, new TurtleCompareCommand( InteractDirection.DOWN ) );
- case 22:
- {
- // attack
- TurtleSide side = parseSide( args, 0 );
- m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
- return tryCommand( context, TurtleToolCommand.attack( InteractDirection.FORWARD, side ) );
- }
- case 23:
- {
- // attackUp
- TurtleSide side = parseSide( args, 0 );
- m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
- return tryCommand( context, TurtleToolCommand.attack( InteractDirection.UP, side ) );
- }
- case 24:
- {
- // attackDown
- TurtleSide side = parseSide( args, 0 );
- m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
- return tryCommand( context, TurtleToolCommand.attack( InteractDirection.DOWN, side ) );
- }
- case 25:
- {
- // dropUp
- int count = parseCount( args, 0 );
- m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
- return tryCommand( context, new TurtleDropCommand( InteractDirection.UP, count ) );
- }
- case 26:
- {
- // dropDown
- int count = parseCount( args, 0 );
- m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
- return tryCommand( context, new TurtleDropCommand( InteractDirection.DOWN, count ) );
- }
- case 27:
- {
- // suck
- int count = parseCount( args, 0 );
- m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
- return tryCommand( context, new TurtleSuckCommand( InteractDirection.FORWARD, count ) );
- }
- case 28:
- {
- // suckUp
- int count = parseCount( args, 0 );
- m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
- return tryCommand( context, new TurtleSuckCommand( InteractDirection.UP, count ) );
- }
- case 29:
- {
- // suckDown
- int count = parseCount( args, 0 );
- m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
- return tryCommand( context, new TurtleSuckCommand( InteractDirection.DOWN, count ) );
- }
- case 30: // getFuelLevel
- return new Object[] { m_turtle.isFuelNeeded() ? m_turtle.getFuelLevel() : "unlimited" };
- case 31:
- {
- // refuel
- int count = optInt( args, 0, Integer.MAX_VALUE );
- if( count < 0 ) throw new LuaException( "Refuel count " + count + " out of range" );
- return tryCommand( context, new TurtleRefuelCommand( count ) );
- }
- case 32:
- {
- // compareTo
- int slot = parseSlotNumber( args, 0 );
- return tryCommand( context, new TurtleCompareToCommand( slot ) );
- }
- case 33:
- {
- // transferTo
- int slot = parseSlotNumber( args, 0 );
- int count = parseCount( args, 1 );
- return tryCommand( context, new TurtleTransferToCommand( slot, count ) );
- }
- case 34: // getSelectedSlot
- return new Object[] { m_turtle.getSelectedSlot() + 1 };
- case 35: // getFuelLimit
- return new Object[] { m_turtle.isFuelNeeded() ? m_turtle.getFuelLimit() : "unlimited" };
- case 36: // equipLeft
- m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
- return tryCommand( context, new TurtleEquipCommand( TurtleSide.LEFT ) );
- case 37: // equipRight
- m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
- return tryCommand( context, new TurtleEquipCommand( TurtleSide.RIGHT ) );
- case 38: // inspect
- return tryCommand( context, new TurtleInspectCommand( InteractDirection.FORWARD ) );
- case 39: // inspectUp
- return tryCommand( context, new TurtleInspectCommand( InteractDirection.UP ) );
- case 40: // inspectDown
- return tryCommand( context, new TurtleInspectCommand( InteractDirection.DOWN ) );
- case 41: // getItemDetail
- {
- // FIXME: There's a race condition here if the stack is being modified (mutating NBT, etc...)
- // on another thread. The obvious solution is to move this into a command, but some programs rely
- // on this having a 0-tick delay.
- int slot = parseOptionalSlotNumber( args, 0, m_turtle.getSelectedSlot() );
- ItemStack stack = m_turtle.getInventory().getStackInSlot( slot );
- if( stack.isEmpty() ) return new Object[] { null };
-
- Item item = stack.getItem();
- String name = ForgeRegistries.ITEMS.getKey( item ).toString();
- int count = stack.getCount();
-
- Map table = new HashMap<>();
- table.put( "name", name );
- table.put( "count", count );
-
- TurtleActionEvent event = new TurtleInspectItemEvent( m_turtle, stack, table );
- if( MinecraftForge.EVENT_BUS.post( event ) ) return new Object[] { false, event.getFailureMessage() };
-
- return new Object[] { table };
- }
-
- default:
- return null;
- }
- }
}
diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java
index 44ab1e5b8..28eaded9b 100644
--- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java
+++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java
@@ -8,8 +8,8 @@
import com.google.common.base.Objects;
import com.mojang.authlib.GameProfile;
import dan200.computercraft.ComputerCraft;
-import dan200.computercraft.api.lua.ILuaContext;
-import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.ILuaCallback;
+import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.turtle.*;
import dan200.computercraft.core.computer.ComputerSide;
@@ -517,27 +517,13 @@ private int issueCommand( ITurtleCommand command )
@Nonnull
@Override
- public Object[] executeCommand( @Nonnull ILuaContext context, @Nonnull ITurtleCommand command ) throws LuaException, InterruptedException
+ public MethodResult executeCommand( @Nonnull ITurtleCommand command )
{
if( getWorld().isRemote ) throw new UnsupportedOperationException( "Cannot run commands on the client" );
// Issue command
int commandID = issueCommand( command );
-
- // Wait for response
- while( true )
- {
- Object[] response = context.pullEvent( "turtle_response" );
- if( response.length >= 3 && response[1] instanceof Number && response[2] instanceof Boolean )
- {
- if( ((Number) response[1]).intValue() == commandID )
- {
- Object[] returnValues = new Object[response.length - 2];
- System.arraycopy( response, 2, returnValues, 0, returnValues.length );
- return returnValues;
- }
- }
- }
+ return new CommandCallback( commandID ).pull;
}
@Override
@@ -951,4 +937,29 @@ private float getAnimationFraction( float f )
float previous = (float) m_lastAnimationProgress / ANIM_DURATION;
return previous + (next - previous) * f;
}
+
+ private static final class CommandCallback implements ILuaCallback
+ {
+ final MethodResult pull = MethodResult.pullEvent( "turtle_response", this );
+ private final int command;
+
+ CommandCallback( int command )
+ {
+ this.command = command;
+ }
+
+ @Nonnull
+ @Override
+ public MethodResult resume( Object[] response )
+ {
+ if( response.length < 3 || !(response[1] instanceof Number) || !(response[2] instanceof Boolean) )
+ {
+ return pull;
+ }
+
+ if( ((Number) response[1]).intValue() != command ) return pull;
+
+ return MethodResult.of( Arrays.copyOfRange( response, 2, response.length ) );
+ }
+ }
}
diff --git a/src/main/java/dan200/computercraft/shared/turtle/upgrades/CraftingTablePeripheral.java b/src/main/java/dan200/computercraft/shared/turtle/upgrades/CraftingTablePeripheral.java
index f33762694..9b5238f85 100644
--- a/src/main/java/dan200/computercraft/shared/turtle/upgrades/CraftingTablePeripheral.java
+++ b/src/main/java/dan200/computercraft/shared/turtle/upgrades/CraftingTablePeripheral.java
@@ -5,16 +5,15 @@
*/
package dan200.computercraft.shared.turtle.upgrades;
-import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
-import dan200.computercraft.api.peripheral.IComputerAccess;
+import dan200.computercraft.api.lua.LuaFunction;
+import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.shared.turtle.core.TurtleCraftCommand;
import javax.annotation.Nonnull;
-
-import static dan200.computercraft.api.lua.ArgumentHelper.optInt;
+import java.util.Optional;
public class CraftingTablePeripheral implements IPeripheral
{
@@ -32,41 +31,17 @@ public String getType()
return "workbench";
}
- @Nonnull
- @Override
- public String[] getMethodNames()
+ @LuaFunction
+ public final MethodResult craft( Optional count ) throws LuaException
{
- return new String[] {
- "craft",
- };
- }
-
- private static int parseCount( Object[] arguments ) throws LuaException
- {
- int count = optInt( arguments, 0, 64 );
- if( count < 0 || count > 64 ) throw new LuaException( "Crafting count " + count + " out of range" );
- return count;
- }
-
- @Override
- public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
- {
- switch( method )
- {
- case 0:
- {
- // craft
- final int limit = parseCount( arguments );
- return turtle.executeCommand( context, new TurtleCraftCommand( limit ) );
- }
- default:
- return null;
- }
+ int limit = count.orElse( 65 );
+ if( limit < 0 || limit > 64 ) throw new LuaException( "Crafting count " + limit + " out of range" );
+ return turtle.executeCommand( new TurtleCraftCommand( limit ) );
}
@Override
public boolean equals( IPeripheral other )
{
- return this == other || other instanceof CraftingTablePeripheral;
+ return other instanceof CraftingTablePeripheral;
}
}
diff --git a/src/main/java/dan200/computercraft/shared/util/StringUtil.java b/src/main/java/dan200/computercraft/shared/util/StringUtil.java
index 57ea7d488..64a7f1e2d 100644
--- a/src/main/java/dan200/computercraft/shared/util/StringUtil.java
+++ b/src/main/java/dan200/computercraft/shared/util/StringUtil.java
@@ -5,6 +5,8 @@
*/
package dan200.computercraft.shared.util;
+import javax.annotation.Nullable;
+
public final class StringUtil
{
private StringUtil() {}
@@ -31,16 +33,8 @@ public static String normaliseLabel( String label )
return builder.toString();
}
- public static byte[] encodeString( String string )
+ public static String toString( @Nullable Object value )
{
- byte[] chars = new byte[string.length()];
-
- for( int i = 0; i < chars.length; i++ )
- {
- char c = string.charAt( i );
- chars[i] = c < 256 ? (byte) c : 63;
- }
-
- return chars;
+ return value == null ? "" : value.toString();
}
}
diff --git a/src/test/java/dan200/computercraft/ContramapMatcher.java b/src/test/java/dan200/computercraft/ContramapMatcher.java
new file mode 100644
index 000000000..35e7117fa
--- /dev/null
+++ b/src/test/java/dan200/computercraft/ContramapMatcher.java
@@ -0,0 +1,54 @@
+/*
+ * This file is part of ComputerCraft - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
+ * Send enquiries to dratcliffe@gmail.com
+ */
+
+package dan200.computercraft;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+
+import java.util.function.Function;
+
+public class ContramapMatcher extends TypeSafeDiagnosingMatcher
+{
+ private final String desc;
+ private final Function convert;
+ private final Matcher matcher;
+
+ public ContramapMatcher( String desc, Function convert, Matcher matcher )
+ {
+ this.desc = desc;
+ this.convert = convert;
+ this.matcher = matcher;
+ }
+
+ @Override
+ protected boolean matchesSafely( T item, Description mismatchDescription )
+ {
+ U converted = convert.apply( item );
+ if( matcher.matches( converted ) ) return true;
+
+ mismatchDescription.appendText( desc ).appendText( " " );
+ matcher.describeMismatch( converted, mismatchDescription );
+ return false;
+ }
+
+ @Override
+ public void describeTo( Description description )
+ {
+ description.appendText( desc ).appendText( " " ).appendDescriptionOf( matcher );
+ }
+
+ public static Matcher contramap( Matcher matcher, String desc, Function convert )
+ {
+ return new ContramapMatcher<>( desc, convert, matcher );
+ }
+
+ public static Matcher contramap( Matcher matcher, Function convert )
+ {
+ return new ContramapMatcher<>( "-f(_)->", convert, matcher );
+ }
+}
diff --git a/src/test/java/dan200/computercraft/core/ComputerTestDelegate.java b/src/test/java/dan200/computercraft/core/ComputerTestDelegate.java
index f2d2453a7..91fdc4cad 100644
--- a/src/test/java/dan200/computercraft/core/ComputerTestDelegate.java
+++ b/src/test/java/dan200/computercraft/core/ComputerTestDelegate.java
@@ -8,8 +8,8 @@
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.ILuaAPI;
-import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.BasicEnvironment;
import dan200.computercraft.core.computer.Computer;
@@ -44,8 +44,7 @@
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Stream;
-import static dan200.computercraft.api.lua.ArgumentHelper.getTable;
-import static dan200.computercraft.api.lua.ArgumentHelper.getType;
+import static dan200.computercraft.api.lua.LuaValues.getType;
/**
* Loads tests from {@code test-rom/spec} and executes them.
@@ -107,189 +106,7 @@ public void before() throws IOException
computer = new Computer( new BasicEnvironment( mount ), term, 0 );
computer.getEnvironment().setPeripheral( ComputerSide.TOP, new FakeModem() );
- computer.addApi( new ILuaAPI()
- {
- @Override
- public String[] getNames()
- {
- return new String[] { "cct_test" };
- }
-
- @Nonnull
- @Override
- public String[] getMethodNames()
- {
- return new String[] { "start", "submit", "finish" };
- }
-
- @Override
- public void startup()
- {
- try
- {
- computer.getAPIEnvironment().getFileSystem().mount(
- "test-rom", "test-rom",
- BasicEnvironment.createMount( ComputerTestDelegate.class, "test-rom", "test" )
- );
- }
- catch( FileSystemException e )
- {
- throw new IllegalStateException( e );
- }
- }
-
- @Nullable
- @Override
- public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
- {
- switch( method )
- {
- case 0: // start: Submit several tests and signal for #get to run
- {
- LOG.info( "Received tests from computer" );
- DynamicNodeBuilder root = new DynamicNodeBuilder( "" );
- for( Object key : getTable( arguments, 0 ).keySet() )
- {
- if( !(key instanceof String) ) throw new LuaException( "Non-key string " + getType( key ) );
-
- String name = (String) key;
- String[] parts = name.split( "\0" );
- DynamicNodeBuilder builder = root;
- for( int i = 0; i < parts.length - 1; i++ ) builder = builder.get( parts[i] );
- builder.runs( parts[parts.length - 1], () -> {
- // Run it
- lock.lockInterruptibly();
- try
- {
- // Set the current test
- runResult = null;
- runFinished = false;
- currentTest = name;
-
- // Tell the computer to run it
- LOG.info( "Starting '{}'", formatName( name ) );
- computer.queueEvent( "cct_test_run", new Object[] { name } );
-
- long remaining = TIMEOUT;
- while( remaining > 0 && computer.isOn() && !runFinished )
- {
- tick();
-
- long waiting = hasRun.awaitNanos( TICK_TIME );
- if( waiting > 0 ) break;
- remaining -= TICK_TIME;
- }
-
- LOG.info( "Finished '{}'", formatName( name ) );
-
- if( remaining <= 0 )
- {
- throw new IllegalStateException( "Timed out waiting for test" );
- }
- else if( !computer.isOn() )
- {
- throw new IllegalStateException( "Computer turned off mid-execution" );
- }
-
- if( runResult != null ) throw runResult;
- }
- finally
- {
- lock.unlock();
- currentTest = null;
- }
- } );
- }
-
- lock.lockInterruptibly();
- try
- {
- tests = root;
- hasTests.signal();
- }
- finally
- {
- lock.unlock();
- }
-
- return null;
- }
- case 1: // submit: Submit the result of a test, allowing the test executor to continue
- {
- Map, ?> tbl = getTable( arguments, 0 );
- String name = (String) tbl.get( "name" );
- String status = (String) tbl.get( "status" );
- String message = (String) tbl.get( "message" );
- String trace = (String) tbl.get( "trace" );
-
- StringBuilder wholeMessage = new StringBuilder();
- if( message != null ) wholeMessage.append( message );
- if( trace != null )
- {
- if( wholeMessage.length() != 0 ) wholeMessage.append( '\n' );
- wholeMessage.append( trace );
- }
-
- lock.lockInterruptibly();
- try
- {
- LOG.info( "'{}' finished with {}", formatName( name ), status );
-
- // Skip if a test mismatch
- if( !name.equals( currentTest ) )
- {
- LOG.warn( "Skipping test '{}', as we're currently executing '{}'", formatName( name ), formatName( currentTest ) );
- return null;
- }
-
- switch( status )
- {
- case "ok":
- case "pending":
- break;
- case "fail":
- runResult = new AssertionFailedError( wholeMessage.toString() );
- break;
- case "error":
- runResult = new IllegalStateException( wholeMessage.toString() );
- break;
- }
-
- runFinished = true;
- hasRun.signal();
- }
- finally
- {
- lock.unlock();
- }
-
- return null;
- }
- case 2: // finish: Signal to after that execution has finished
- LOG.info( "Finished" );
- lock.lockInterruptibly();
- try
- {
- finished = true;
- if( arguments.length > 0 )
- {
- @SuppressWarnings( "unchecked" )
- Map> finished = (Map>) arguments[0];
- finishedWith = finished;
- }
-
- hasFinished.signal();
- }
- finally
- {
- lock.unlock();
- }
- return null;
- default:
- return null;
- }
- }
- } );
+ computer.addApi( new CctTestAPI() );
computer.turnOn();
}
@@ -475,4 +292,198 @@ public boolean equals( @Nullable IPeripheral other )
return this == other;
}
}
+
+ public class CctTestAPI implements ILuaAPI
+ {
+ @Override
+ public String[] getNames()
+ {
+ return new String[] { "cct_test" };
+ }
+
+ @Override
+ public void startup()
+ {
+ try
+ {
+ computer.getAPIEnvironment().getFileSystem().mount(
+ "test-rom", "test-rom",
+ BasicEnvironment.createMount( ComputerTestDelegate.class, "test-rom", "test" )
+ );
+ }
+ catch( FileSystemException e )
+ {
+ throw new IllegalStateException( e );
+ }
+ }
+
+ @LuaFunction
+ public final void start( Map, ?> tests ) throws LuaException
+ {
+ // Submit several tests and signal for #get to run
+ LOG.info( "Received tests from computer" );
+ DynamicNodeBuilder root = new DynamicNodeBuilder( "" );
+ for( Object key : tests.keySet() )
+ {
+ if( !(key instanceof String) ) throw new LuaException( "Non-key string " + getType( key ) );
+
+ String name = (String) key;
+ String[] parts = name.split( "\0" );
+ DynamicNodeBuilder builder = root;
+ for( int i = 0; i < parts.length - 1; i++ ) builder = builder.get( parts[i] );
+ builder.runs( parts[parts.length - 1], () -> {
+ // Run it
+ lock.lockInterruptibly();
+ try
+ {
+ // Set the current test
+ runResult = null;
+ runFinished = false;
+ currentTest = name;
+
+ // Tell the computer to run it
+ LOG.info( "Starting '{}'", formatName( name ) );
+ computer.queueEvent( "cct_test_run", new Object[] { name } );
+
+ long remaining = TIMEOUT;
+ while( remaining > 0 && computer.isOn() && !runFinished )
+ {
+ tick();
+
+ long waiting = hasRun.awaitNanos( TICK_TIME );
+ if( waiting > 0 ) break;
+ remaining -= TICK_TIME;
+ }
+
+ LOG.info( "Finished '{}'", formatName( name ) );
+
+ if( remaining <= 0 )
+ {
+ throw new IllegalStateException( "Timed out waiting for test" );
+ }
+ else if( !computer.isOn() )
+ {
+ throw new IllegalStateException( "Computer turned off mid-execution" );
+ }
+
+ if( runResult != null ) throw runResult;
+ }
+ finally
+ {
+ lock.unlock();
+ currentTest = null;
+ }
+ } );
+ }
+
+ try
+ {
+ lock.lockInterruptibly();
+ }
+ catch( InterruptedException e )
+ {
+ throw new RuntimeException( e );
+ }
+ try
+ {
+ ComputerTestDelegate.this.tests = root;
+ hasTests.signal();
+ }
+ finally
+ {
+ lock.unlock();
+ }
+ }
+
+ @LuaFunction
+ public final void submit( Map, ?> tbl )
+ {
+ // Submit the result of a test, allowing the test executor to continue
+ String name = (String) tbl.get( "name" );
+ if( name == null )
+ {
+ ComputerCraft.log.error( "Oh no: {}", tbl );
+ }
+ String status = (String) tbl.get( "status" );
+ String message = (String) tbl.get( "message" );
+ String trace = (String) tbl.get( "trace" );
+
+ StringBuilder wholeMessage = new StringBuilder();
+ if( message != null ) wholeMessage.append( message );
+ if( trace != null )
+ {
+ if( wholeMessage.length() != 0 ) wholeMessage.append( '\n' );
+ wholeMessage.append( trace );
+ }
+
+ try
+ {
+ lock.lockInterruptibly();
+ }
+ catch( InterruptedException e )
+ {
+ throw new RuntimeException( e );
+ }
+ try
+ {
+ LOG.info( "'{}' finished with {}", formatName( name ), status );
+
+ // Skip if a test mismatch
+ if( !name.equals( currentTest ) )
+ {
+ LOG.warn( "Skipping test '{}', as we're currently executing '{}'", formatName( name ), formatName( currentTest ) );
+ return;
+ }
+
+ switch( status )
+ {
+ case "ok":
+ case "pending":
+ break;
+ case "fail":
+ runResult = new AssertionFailedError( wholeMessage.toString() );
+ break;
+ case "error":
+ runResult = new IllegalStateException( wholeMessage.toString() );
+ break;
+ }
+
+ runFinished = true;
+ hasRun.signal();
+ }
+ finally
+ {
+ lock.unlock();
+ }
+ }
+
+ @LuaFunction
+ public final void finish( Optional> result )
+ {
+ @SuppressWarnings( "unchecked" )
+ Map> finishedResult = (Map>) result.orElse( null );
+ LOG.info( "Finished" );
+
+ // Signal to after that execution has finished
+ try
+ {
+ lock.lockInterruptibly();
+ }
+ catch( InterruptedException e )
+ {
+ throw new RuntimeException( e );
+ }
+ try
+ {
+ finished = true;
+ if( finishedResult != null ) finishedWith = finishedResult;
+
+ hasFinished.signal();
+ }
+ finally
+ {
+ lock.unlock();
+ }
+ }
+ }
}
diff --git a/src/test/java/dan200/computercraft/core/apis/ObjectWrapper.java b/src/test/java/dan200/computercraft/core/apis/ObjectWrapper.java
index cd7684270..a01cc639a 100644
--- a/src/test/java/dan200/computercraft/core/apis/ObjectWrapper.java
+++ b/src/test/java/dan200/computercraft/core/apis/ObjectWrapper.java
@@ -5,52 +5,47 @@
*/
package dan200.computercraft.core.apis;
-import dan200.computercraft.api.lua.ILuaContext;
-import dan200.computercraft.api.lua.ILuaObject;
-import dan200.computercraft.api.lua.ILuaTask;
-import dan200.computercraft.api.lua.LuaException;
+import dan200.computercraft.api.lua.*;
+import dan200.computercraft.core.asm.LuaMethod;
+import dan200.computercraft.core.asm.NamedMethod;
import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
public class ObjectWrapper implements ILuaContext
{
- private final ILuaObject object;
- private final String[] methods;
+ private final Object object;
+ private final Map methodMap;
- public ObjectWrapper( ILuaObject object )
+ public ObjectWrapper( Object object )
{
this.object = object;
- this.methods = object.getMethodNames();
- }
+ String[] dynamicMethods = object instanceof IDynamicLuaObject
+ ? Objects.requireNonNull( ((IDynamicLuaObject) object).getMethodNames(), "Methods cannot be null" )
+ : LuaMethod.EMPTY_METHODS;
- private int findMethod( String method )
- {
- for( int i = 0; i < methods.length; i++ )
+ List> methods = LuaMethod.GENERATOR.getMethods( object.getClass() );
+
+ Map methodMap = this.methodMap = new HashMap<>( methods.size() + dynamicMethods.length );
+ for( int i = 0; i < dynamicMethods.length; i++ )
{
- if( method.equals( methods[i] ) ) return i;
+ methodMap.put( dynamicMethods[i], LuaMethod.DYNAMIC.get( i ) );
+ }
+ for( NamedMethod method : methods )
+ {
+ methodMap.put( method.getName(), method.getMethod() );
}
- return -1;
- }
-
- public boolean hasMethod( String method )
- {
- return findMethod( method ) >= 0;
}
public Object[] call( String name, Object... args ) throws LuaException
{
- int method = findMethod( name );
- if( method < 0 ) throw new IllegalStateException( "No such method '" + name + "'" );
+ LuaMethod method = methodMap.get( name );
+ if( method == null ) throw new IllegalStateException( "No such method '" + name + "'" );
- try
- {
- return object.callMethod( this, method, args );
- }
- catch( InterruptedException e )
- {
- throw new IllegalStateException( "Should never be interrupted", e );
- }
+ return method.apply( object, this, new ObjectArguments( args ) ).getResult();
}
@SuppressWarnings( "unchecked" )
@@ -64,34 +59,6 @@ public T callOf( Class klass, String name, Object... args ) throws LuaExc
return klass.cast( call( name, args )[0] );
}
- @Nonnull
- @Override
- public Object[] pullEvent( @Nullable String filter )
- {
- throw new IllegalStateException( "Method should never yield" );
- }
-
- @Nonnull
- @Override
- public Object[] pullEventRaw( @Nullable String filter )
- {
- throw new IllegalStateException( "Method should never yield" );
- }
-
- @Nonnull
- @Override
- public Object[] yield( @Nullable Object[] arguments )
- {
- throw new IllegalStateException( "Method should never yield" );
- }
-
- @Nullable
- @Override
- public Object[] executeMainThreadTask( @Nonnull ILuaTask task )
- {
- throw new IllegalStateException( "Method should never yield" );
- }
-
@Override
public long issueMainThreadTask( @Nonnull ILuaTask task )
{
diff --git a/src/test/java/dan200/computercraft/core/apis/handles/BinaryReadableHandleTest.java b/src/test/java/dan200/computercraft/core/apis/handles/BinaryReadableHandleTest.java
index 3def2ce9f..ad272c9aa 100644
--- a/src/test/java/dan200/computercraft/core/apis/handles/BinaryReadableHandleTest.java
+++ b/src/test/java/dan200/computercraft/core/apis/handles/BinaryReadableHandleTest.java
@@ -9,6 +9,7 @@
import dan200.computercraft.core.apis.ObjectWrapper;
import org.junit.jupiter.api.Test;
+import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
@@ -27,17 +28,16 @@ public void testReadChar() throws LuaException
public void testReadShortComplete() throws LuaException
{
ObjectWrapper wrapper = fromLength( 10 );
- assertEquals( 5, wrapper.callOf( "read", 5 ).length );
+ assertEquals( 5, wrapper.callOf( "read", 5 ).remaining() );
}
@Test
public void testReadShortPartial() throws LuaException
{
ObjectWrapper wrapper = fromLength( 5 );
- assertEquals( 5, wrapper.callOf( "read", 10 ).length );
+ assertEquals( 5, wrapper.callOf( "read", 10 ).remaining() );
}
-
@Test
public void testReadLongComplete() throws LuaException
{
@@ -56,13 +56,13 @@ public void testReadLongPartial() throws LuaException
public void testReadLongPartialSmaller() throws LuaException
{
ObjectWrapper wrapper = fromLength( 1000 );
- assertEquals( 1000, wrapper.callOf( "read", 11000 ).length );
+ assertEquals( 1000, wrapper.callOf( "read", 11000 ).remaining() );
}
@Test
public void testReadLine() throws LuaException
{
- ObjectWrapper wrapper = new ObjectWrapper( new BinaryReadableHandle( new ArrayByteChannel( "hello\r\nworld\r!".getBytes( StandardCharsets.UTF_8 ) ) ) );
+ ObjectWrapper wrapper = new ObjectWrapper( BinaryReadableHandle.of( new ArrayByteChannel( "hello\r\nworld\r!".getBytes( StandardCharsets.UTF_8 ) ) ) );
assertArrayEquals( "hello".getBytes( StandardCharsets.UTF_8 ), wrapper.callOf( "readLine" ) );
assertArrayEquals( "world\r!".getBytes( StandardCharsets.UTF_8 ), wrapper.callOf( "readLine" ) );
assertNull( wrapper.call( "readLine" ) );
@@ -71,7 +71,7 @@ public void testReadLine() throws LuaException
@Test
public void testReadLineTrailing() throws LuaException
{
- ObjectWrapper wrapper = new ObjectWrapper( new BinaryReadableHandle( new ArrayByteChannel( "hello\r\nworld\r!".getBytes( StandardCharsets.UTF_8 ) ) ) );
+ ObjectWrapper wrapper = new ObjectWrapper( BinaryReadableHandle.of( new ArrayByteChannel( "hello\r\nworld\r!".getBytes( StandardCharsets.UTF_8 ) ) ) );
assertArrayEquals( "hello\r\n".getBytes( StandardCharsets.UTF_8 ), wrapper.callOf( "readLine", true ) );
assertArrayEquals( "world\r!".getBytes( StandardCharsets.UTF_8 ), wrapper.callOf( "readLine", true ) );
assertNull( wrapper.call( "readLine", true ) );
@@ -81,6 +81,6 @@ private static ObjectWrapper fromLength( int length )
{
byte[] input = new byte[length];
Arrays.fill( input, (byte) 'A' );
- return new ObjectWrapper( new BinaryReadableHandle( new ArrayByteChannel( input ) ) );
+ return new ObjectWrapper( BinaryReadableHandle.of( new ArrayByteChannel( input ) ) );
}
}
diff --git a/src/test/java/dan200/computercraft/core/asm/GeneratorTest.java b/src/test/java/dan200/computercraft/core/asm/GeneratorTest.java
new file mode 100644
index 000000000..8104fa4a1
--- /dev/null
+++ b/src/test/java/dan200/computercraft/core/asm/GeneratorTest.java
@@ -0,0 +1,253 @@
+/*
+ * This file is part of ComputerCraft - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
+ * Send enquiries to dratcliffe@gmail.com
+ */
+
+package dan200.computercraft.core.asm;
+
+import dan200.computercraft.api.lua.*;
+import dan200.computercraft.api.peripheral.IComputerAccess;
+import dan200.computercraft.core.computer.ComputerSide;
+import org.hamcrest.Matcher;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import static dan200.computercraft.ContramapMatcher.contramap;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class GeneratorTest
+{
+ @Test
+ public void testBasic()
+ {
+ List> methods = LuaMethod.GENERATOR.getMethods( Basic.class );
+ assertThat( methods, contains(
+ allOf(
+ named( "go" ),
+ contramap( is( true ), "non-yielding", NamedMethod::nonYielding )
+ )
+ ) );
+ }
+
+ @Test
+ public void testIdentical()
+ {
+ List> methods = LuaMethod.GENERATOR.getMethods( Basic.class );
+ List> methods2 = LuaMethod.GENERATOR.getMethods( Basic.class );
+ assertThat( methods, sameInstance( methods2 ) );
+ }
+
+ @Test
+ public void testIdenticalMethods()
+ {
+ List> methods = LuaMethod.GENERATOR.getMethods( Basic.class );
+ List> methods2 = LuaMethod.GENERATOR.getMethods( Basic2.class );
+ assertThat( methods, contains( named( "go" ) ) );
+ assertThat( methods.get( 0 ).getMethod(), sameInstance( methods2.get( 0 ).getMethod() ) );
+ }
+
+ @Test
+ public void testEmptyClass()
+ {
+ assertThat( LuaMethod.GENERATOR.getMethods( Empty.class ), is( empty() ) );
+ }
+
+ @Test
+ public void testNonPublicClass()
+ {
+ assertThat( LuaMethod.GENERATOR.getMethods( NonPublic.class ), is( empty() ) );
+ }
+
+ @Test
+ public void testNonInstance()
+ {
+ assertThat( LuaMethod.GENERATOR.getMethods( NonInstance.class ), is( empty() ) );
+ }
+
+ @Test
+ public void testIllegalThrows()
+ {
+ assertThat( LuaMethod.GENERATOR.getMethods( IllegalThrows.class ), is( empty() ) );
+ }
+
+ @Test
+ public void testCustomNames()
+ {
+ List> methods = LuaMethod.GENERATOR.getMethods( CustomNames.class );
+ assertThat( methods, contains( named( "go1" ), named( "go2" ) ) );
+ }
+
+ @Test
+ public void testArgKinds()
+ {
+ List> methods = LuaMethod.GENERATOR.getMethods( ArgKinds.class );
+ assertThat( methods, containsInAnyOrder(
+ named( "objectArg" ), named( "intArg" ), named( "optIntArg" ),
+ named( "context" ), named( "arguments" )
+ ) );
+ }
+
+ @Test
+ public void testEnum() throws LuaException
+ {
+ List> methods = LuaMethod.GENERATOR.getMethods( EnumMethods.class );
+ assertThat( methods, containsInAnyOrder( named( "getEnum" ), named( "optEnum" ) ) );
+
+ assertThat( apply( methods, new EnumMethods(), "getEnum", "front" ), one( is( "FRONT" ) ) );
+ assertThat( apply( methods, new EnumMethods(), "optEnum", "front" ), one( is( "FRONT" ) ) );
+ assertThat( apply( methods, new EnumMethods(), "optEnum" ), one( is( "?" ) ) );
+ assertThrows( LuaException.class, () -> apply( methods, new EnumMethods(), "getEnum", "not as side" ) );
+ }
+
+ @Test
+ public void testMainThread() throws LuaException
+ {
+ List> methods = LuaMethod.GENERATOR.getMethods( MainThread.class );
+ assertThat( methods, contains( allOf(
+ named( "go" ),
+ contramap( is( false ), "non-yielding", NamedMethod::nonYielding )
+ ) ) );
+
+ assertThat( apply( methods, new MainThread(), "go" ),
+ contramap( notNullValue(), "callback", MethodResult::getCallback ) );
+ }
+
+ public static class Basic
+ {
+ @LuaFunction
+ public final void go()
+ { }
+ }
+
+ public static class Basic2 extends Basic
+ {
+ }
+
+ public static class Empty
+ {
+ }
+
+ static class NonPublic
+ {
+ @LuaFunction
+ public final void go()
+ { }
+ }
+
+ public static class NonInstance
+ {
+ @LuaFunction
+ public static void go()
+ { }
+ }
+
+ public static class IllegalThrows
+ {
+ @LuaFunction
+ public final void go() throws IOException
+ {
+ throw new IOException();
+ }
+ }
+
+ public static class CustomNames
+ {
+ @LuaFunction( { "go1", "go2" } )
+ public final void go()
+ { }
+ }
+
+ public static class ArgKinds
+ {
+ @LuaFunction
+ public final void objectArg( Object arg )
+ { }
+
+ @LuaFunction
+ public final void intArg( int arg )
+ { }
+
+ @LuaFunction
+ public final void optIntArg( Optional arg )
+ { }
+
+ @LuaFunction
+ public final void context( ILuaContext arg )
+ { }
+
+ @LuaFunction
+ public final void arguments( IArguments arg )
+ { }
+
+ @LuaFunction
+ public final void unknown( IComputerAccess arg )
+ { }
+
+ @LuaFunction
+ public final void illegalMap( Map arg )
+ { }
+
+ @LuaFunction
+ public final void optIllegalMap( Optional> arg )
+ { }
+ }
+
+ public static class EnumMethods
+ {
+ @LuaFunction
+ public final String getEnum( ComputerSide side )
+ {
+ return side.name();
+ }
+
+ @LuaFunction
+ public final String optEnum( Optional side )
+ {
+ return side.map( ComputerSide::name ).orElse( "?" );
+ }
+ }
+
+ public static class MainThread
+ {
+ @LuaFunction( mainThread = true )
+ public final void go()
+ { }
+ }
+
+ private static T find( Collection> methods, String name )
+ {
+ return methods.stream()
+ .filter( x -> x.getName().equals( name ) )
+ .map( NamedMethod::getMethod )
+ .findAny()
+ .orElseThrow( NullPointerException::new );
+ }
+
+ public static MethodResult apply( Collection> methods, Object instance, String name, Object... args ) throws LuaException
+ {
+ return find( methods, name ).apply( instance, CONTEXT, new ObjectArguments( args ) );
+ }
+
+ public static Matcher one( Matcher object )
+ {
+ return allOf(
+ contramap( nullValue(), "callback", MethodResult::getCallback ),
+ contramap( array( object ), "result", MethodResult::getResult )
+ );
+ }
+
+ public static Matcher> named( String method )
+ {
+ return contramap( is( method ), "name", NamedMethod::getName );
+ }
+
+ private static final ILuaContext CONTEXT = task -> 0;
+}
diff --git a/src/test/java/dan200/computercraft/core/asm/MethodTest.java b/src/test/java/dan200/computercraft/core/asm/MethodTest.java
new file mode 100644
index 000000000..58be91ecd
--- /dev/null
+++ b/src/test/java/dan200/computercraft/core/asm/MethodTest.java
@@ -0,0 +1,208 @@
+/*
+ * This file is part of ComputerCraft - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
+ * Send enquiries to dratcliffe@gmail.com
+ */
+
+package dan200.computercraft.core.asm;
+
+import dan200.computercraft.api.lua.*;
+import dan200.computercraft.api.peripheral.IComputerAccess;
+import dan200.computercraft.api.peripheral.IDynamicPeripheral;
+import dan200.computercraft.api.peripheral.IPeripheral;
+import dan200.computercraft.core.computer.ComputerBootstrap;
+import dan200.computercraft.core.computer.ComputerSide;
+import org.junit.jupiter.api.Test;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.Collections;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+public class MethodTest
+{
+ @Test
+ public void testMainThread()
+ {
+ ComputerBootstrap.run( "assert(main_thread.go() == 123)", x -> x.addApi( new MainThread() ), 50 );
+ }
+
+ @Test
+ public void testMainThreadPeripheral()
+ {
+ ComputerBootstrap.run( "assert(peripheral.call('top', 'go') == 123)",
+ x -> x.getEnvironment().setPeripheral( ComputerSide.TOP, new MainThread() ),
+ 50 );
+ }
+
+ @Test
+ public void testDynamic()
+ {
+ ComputerBootstrap.run(
+ "assert(dynamic.foo() == 123, 'foo: ' .. tostring(dynamic.foo()))\n" +
+ "assert(dynamic.bar() == 321, 'bar: ' .. tostring(dynamic.bar()))",
+ x -> x.addApi( new Dynamic() ), 50 );
+ }
+
+ @Test
+ public void testDynamicPeripheral()
+ {
+ ComputerBootstrap.run(
+ "local dynamic = peripheral.wrap('top')\n" +
+ "assert(dynamic.foo() == 123, 'foo: ' .. tostring(dynamic.foo()))\n" +
+ "assert(dynamic.bar() == 321, 'bar: ' .. tostring(dynamic.bar()))",
+ x -> x.getEnvironment().setPeripheral( ComputerSide.TOP, new Dynamic() ),
+ 50
+ );
+ }
+
+ @Test
+ public void testExtra()
+ {
+ ComputerBootstrap.run( "assert(extra.go, 'go')\nassert(extra.go2, 'go2')",
+ x -> x.addApi( new ExtraObject() ),
+ 50 );
+ }
+
+ @Test
+ public void testPeripheralThrow()
+ {
+ ComputerBootstrap.run(
+ "local throw = peripheral.wrap('top')\n" +
+ "local _, err = pcall(throw.thisThread) assert(err == 'pcall: !', err)\n" +
+ "local _, err = pcall(throw.mainThread) assert(err == 'pcall: !', err)",
+ x -> x.getEnvironment().setPeripheral( ComputerSide.TOP, new PeripheralThrow() ),
+ 50
+ );
+ }
+
+ public static class MainThread implements ILuaAPI, IPeripheral
+ {
+ public final String thread = Thread.currentThread().getName();
+
+ @Override
+ public String[] getNames()
+ {
+ return new String[] { "main_thread" };
+ }
+
+ @LuaFunction( mainThread = true )
+ public final int go()
+ {
+ assertThat( Thread.currentThread().getName(), is( thread ) );
+ return 123;
+ }
+
+ @Nonnull
+ @Override
+ public String getType()
+ {
+ return "main_thread";
+ }
+
+ @Override
+ public boolean equals( @Nullable IPeripheral other )
+ {
+ return this == other;
+ }
+ }
+
+ public static class Dynamic implements IDynamicLuaObject, ILuaAPI, IDynamicPeripheral
+ {
+ @Nonnull
+ @Override
+ public String[] getMethodNames()
+ {
+ return new String[] { "foo" };
+ }
+
+ @Nonnull
+ @Override
+ public MethodResult callMethod( @Nonnull ILuaContext context, int method, @Nonnull IArguments arguments )
+ {
+ return MethodResult.of( 123 );
+ }
+
+ @Nonnull
+ @Override
+ public MethodResult callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull IArguments arguments )
+ {
+ return callMethod( context, method, arguments );
+ }
+
+ @LuaFunction
+ public final int bar()
+ {
+ return 321;
+ }
+
+ @Override
+ public String[] getNames()
+ {
+ return new String[] { "dynamic" };
+ }
+
+ @Nonnull
+ @Override
+ public String getType()
+ {
+ return "dynamic";
+ }
+
+ @Override
+ public boolean equals( @Nullable IPeripheral other )
+ {
+ return this == other;
+ }
+ }
+
+ public static class ExtraObject implements ObjectSource, ILuaAPI
+ {
+ @Override
+ public String[] getNames()
+ {
+ return new String[] { "extra" };
+ }
+
+ @LuaFunction
+ public final void go2()
+ {
+ }
+
+ @Override
+ public Iterable getExtra()
+ {
+ return Collections.singletonList( new GeneratorTest.Basic() );
+ }
+ }
+
+ public static class PeripheralThrow implements IPeripheral
+ {
+ @LuaFunction
+ public final void thisThread() throws LuaException
+ {
+ throw new LuaException( "!" );
+ }
+
+ @LuaFunction( mainThread = true )
+ public final void mainThread() throws LuaException
+ {
+ throw new LuaException( "!" );
+ }
+
+ @Nonnull
+ @Override
+ public String getType()
+ {
+ return "throw";
+ }
+
+ @Override
+ public boolean equals( @Nullable IPeripheral other )
+ {
+ return this == other;
+ }
+ }
+}
diff --git a/src/test/java/dan200/computercraft/core/computer/ComputerBootstrap.java b/src/test/java/dan200/computercraft/core/computer/ComputerBootstrap.java
index 6ac8a7b55..e2deba3cb 100644
--- a/src/test/java/dan200/computercraft/core/computer/ComputerBootstrap.java
+++ b/src/test/java/dan200/computercraft/core/computer/ComputerBootstrap.java
@@ -7,16 +7,14 @@
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.filesystem.IWritableMount;
+import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.ILuaAPI;
-import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
-import dan200.computercraft.api.lua.ArgumentHelper;
+import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.filesystem.MemoryMount;
import dan200.computercraft.core.terminal.Terminal;
import org.junit.jupiter.api.Assertions;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.function.Consumer;
@@ -26,20 +24,26 @@
public class ComputerBootstrap
{
private static final int TPS = 20;
- private static final int MAX_TIME = 10;
+ public static final int MAX_TIME = 10;
- public static void run( String program )
+ public static void run( String program, Consumer setup, int maxTimes )
{
MemoryMount mount = new MemoryMount()
.addFile( "test.lua", program )
- .addFile( "startup", "assertion.assert(pcall(loadfile('test.lua', nil, _ENV))) os.shutdown()" );
+ .addFile( "startup.lua", "assertion.assert(pcall(loadfile('test.lua', nil, _ENV))) os.shutdown()" );
- run( mount, x -> { } );
+ run( mount, setup, maxTimes );
}
- public static void run( IWritableMount mount, Consumer setup )
+ public static void run( String program, int maxTimes )
+ {
+ run( program, x -> { }, maxTimes );
+ }
+
+ public static void run( IWritableMount mount, Consumer setup, int maxTicks )
{
ComputerCraft.logPeripheralErrors = true;
+ ComputerCraft.maxMainComputerTime = ComputerCraft.maxMainGlobalTime = Integer.MAX_VALUE;
Terminal term = new Terminal( ComputerCraft.terminalWidth_computer, ComputerCraft.terminalHeight_computer );
final Computer computer = new Computer( new BasicEnvironment( mount ), term, 0 );
@@ -54,7 +58,7 @@ public static void run( IWritableMount mount, Consumer setup )
computer.turnOn();
boolean everOn = false;
- for( int tick = 0; tick < TPS * MAX_TIME; tick++ )
+ for( int tick = 0; tick < TPS * maxTicks; tick++ )
{
long start = System.currentTimeMillis();
@@ -99,7 +103,7 @@ public static void run( IWritableMount mount, Consumer setup )
}
}
- private static class AssertApi implements ILuaAPI
+ public static class AssertApi implements ILuaAPI
{
boolean didAssert;
String message;
@@ -110,39 +114,25 @@ public String[] getNames()
return new String[] { "assertion" };
}
- @Nonnull
- @Override
- public String[] getMethodNames()
+ @LuaFunction
+ public final void log( IArguments arguments )
{
- return new String[] { "assert", "log" };
+ ComputerCraft.log.info( "[Computer] {}", Arrays.toString( arguments.getAll() ) );
}
- @Nullable
- @Override
- public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
+ @LuaFunction( "assert" )
+ public final Object[] doAssert( IArguments arguments ) throws LuaException
{
- switch( method )
+ didAssert = true;
+
+ Object arg = arguments.get( 0 );
+ if( arg == null || arg == Boolean.FALSE )
{
- case 0: // assert
- {
- didAssert = true;
-
- Object arg = arguments.length >= 1 ? arguments[0] : null;
- if( arg == null || arg == Boolean.FALSE )
- {
- message = ArgumentHelper.optString( arguments, 1, "Assertion failed" );
- throw new LuaException( message );
- }
-
- return arguments;
- }
- case 1:
- ComputerCraft.log.info( "[Computer] {}", Arrays.toString( arguments ) );
- return null;
-
- default:
- return null;
+ message = arguments.optString( 1, "Assertion failed" );
+ throw new LuaException( message );
}
+
+ return arguments.getAll();
}
}
}
diff --git a/src/test/java/dan200/computercraft/core/computer/ComputerTest.java b/src/test/java/dan200/computercraft/core/computer/ComputerTest.java
index d2d6d744c..830e27305 100644
--- a/src/test/java/dan200/computercraft/core/computer/ComputerTest.java
+++ b/src/test/java/dan200/computercraft/core/computer/ComputerTest.java
@@ -5,9 +5,15 @@
*/
package dan200.computercraft.core.computer;
+import com.google.common.io.CharStreams;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+
import static java.time.Duration.ofSeconds;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
@@ -19,7 +25,7 @@ public void testTimeout()
assertTimeoutPreemptively( ofSeconds( 20 ), () -> {
try
{
- ComputerBootstrap.run( "print('Hello') while true do end" );
+ ComputerBootstrap.run( "print('Hello') while true do end", ComputerBootstrap.MAX_TIME );
}
catch( AssertionError e )
{
@@ -30,4 +36,14 @@ public void testTimeout()
Assertions.fail( "Expected computer to timeout" );
} );
}
+
+ public static void main( String[] args ) throws Exception
+ {
+ InputStream stream = ComputerTest.class.getClassLoader().getResourceAsStream( "benchmark.lua" );
+ try( InputStreamReader reader = new InputStreamReader( Objects.requireNonNull( stream ), StandardCharsets.UTF_8 ) )
+ {
+ String contents = CharStreams.toString( reader );
+ ComputerBootstrap.run( contents, 1000 );
+ }
+ }
}
diff --git a/src/test/java/dan200/computercraft/core/filesystem/FileSystemTest.java b/src/test/java/dan200/computercraft/core/filesystem/FileSystemTest.java
index deb403e5a..73aaaccda 100644
--- a/src/test/java/dan200/computercraft/core/filesystem/FileSystemTest.java
+++ b/src/test/java/dan200/computercraft/core/filesystem/FileSystemTest.java
@@ -27,8 +27,8 @@ public class FileSystemTest
* Ensures writing a file truncates it.
*
* @throws FileSystemException When the file system cannot be constructed.
- * @throws LuaException When Lua functions fail.
- * @throws IOException When reading and writing from strings
+ * @throws LuaException When Lua functions fail.
+ * @throws IOException When reading and writing from strings
*/
@Test
public void testWriteTruncates() throws FileSystemException, LuaException, IOException
diff --git a/src/test/java/dan200/computercraft/shared/wired/NetworkTest.java b/src/test/java/dan200/computercraft/shared/wired/NetworkTest.java
index 829432c83..174558001 100644
--- a/src/test/java/dan200/computercraft/shared/wired/NetworkTest.java
+++ b/src/test/java/dan200/computercraft/shared/wired/NetworkTest.java
@@ -8,13 +8,10 @@
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import dan200.computercraft.api.ComputerCraftAPI;
-import dan200.computercraft.api.lua.ILuaContext;
-import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNetwork;
import dan200.computercraft.api.network.wired.IWiredNetworkChange;
import dan200.computercraft.api.network.wired.IWiredNode;
-import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.util.DirectionUtil;
import net.minecraft.util.Direction;
@@ -397,20 +394,6 @@ public String getType()
return "test";
}
- @Nonnull
- @Override
- public String[] getMethodNames()
- {
- return new String[0];
- }
-
- @Nullable
- @Override
- public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
- {
- return new Object[0];
- }
-
@Override
public boolean equals( @Nullable IPeripheral other )
{
diff --git a/src/test/resources/benchmark.lua b/src/test/resources/benchmark.lua
new file mode 100644
index 000000000..dbbef23a1
--- /dev/null
+++ b/src/test/resources/benchmark.lua
@@ -0,0 +1,24 @@
+local function log(msg)
+ print(msg)
+ if assertion then assertion.log(msg) end
+end
+
+local function run(name, n, f, ...)
+ sleep(0)
+ local s = os.epoch("utc")
+ for _ = 1, n do f(...) end
+ local e = os.epoch("utc") - s
+ log(("%10s %.2fs %.fop/s"):format(name, e*1e-3, n/e))
+end
+
+local function run5(...) for _ = 1, 5 do run(...) end end
+
+local native = term.native()
+local x, y = native.getCursorPos()
+
+log("Starting the benchmark")
+run5("redstone.getAnalogInput", 1e7, redstone.getAnalogInput, "top")
+run5("term.getCursorPos", 2e7, native.getCursorPos)
+run5("term.setCursorPos", 2e7, native.setCursorPos, x, y)
+
+if assertion then assertion.assert(true) end
diff --git a/src/test/resources/test-rom/spec/apis/redstone_spec.lua b/src/test/resources/test-rom/spec/apis/redstone_spec.lua
index 17d8acab0..148d08700 100644
--- a/src/test/resources/test-rom/spec/apis/redstone_spec.lua
+++ b/src/test/resources/test-rom/spec/apis/redstone_spec.lua
@@ -2,7 +2,7 @@ local function it_side(func, ...)
local arg = table.pack(...)
it("requires a specific side", function()
expect.error(func, 0):eq("bad argument #1 (string expected, got number)")
- expect.error(func, "blah", table.unpack(arg)):eq("Invalid side.")
+ expect.error(func, "blah", table.unpack(arg)):eq("bad argument #1 (unknown option blah)")
func("top", table.unpack(arg))
func("Top", table.unpack(arg))