diff --git a/build.gradle b/build.gradle index 2ede63c40..f765277ee 100644 --- a/build.gradle +++ b/build.gradle @@ -104,6 +104,7 @@ accessTransformer file('src/main/resources/META-INF/accesstransformer.cfg') } repositories { + mavenLocal() mavenCentral() maven { name "SquidDev" @@ -130,6 +131,7 @@ accessTransformer file('src/main/resources/META-INF/accesstransformer.cfg') compileOnly 'com.google.auto.service:auto-service:1.0-rc7' annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7' + shade 'cc.tweaked.waluaigi:waluaigi:1.0-SNAPSHOT' shade 'org.squiddev:Cobalt:0.5.1-SNAPSHOT' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' diff --git a/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java b/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java index ad9c729c1..3d491af8e 100644 --- a/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java +++ b/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java @@ -13,9 +13,9 @@ import dan200.computercraft.core.apis.*; import dan200.computercraft.core.filesystem.FileSystem; import dan200.computercraft.core.filesystem.FileSystemException; -import dan200.computercraft.core.lua.CobaltLuaMachine; import dan200.computercraft.core.lua.ILuaMachine; import dan200.computercraft.core.lua.MachineResult; +import dan200.computercraft.core.lua.waluaigi.WaluaigiLuaMachine; import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.core.tracking.Tracking; import dan200.computercraft.shared.util.Colour; @@ -386,7 +386,7 @@ private ILuaMachine createLuaMachine() } // Create the lua machine - ILuaMachine machine = new CobaltLuaMachine( computer, timeout ); + ILuaMachine machine = new WaluaigiLuaMachine( computer, timeout ); // 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 ); diff --git a/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java b/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java index d0e58c5a2..54a7efdc6 100644 --- a/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java +++ b/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java @@ -10,7 +10,6 @@ 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; import dan200.computercraft.core.tracking.Tracking; import dan200.computercraft.core.tracking.TrackingField; @@ -53,7 +52,7 @@ public class CobaltLuaMachine implements ILuaMachine private final Computer computer; private final TimeoutState timeout; private final TimeoutDebugHandler debug; - private final ILuaContext context = new CobaltLuaContext(); + private final ILuaContext context; private LuaState state; private LuaTable globals; @@ -65,6 +64,7 @@ public CobaltLuaMachine( Computer computer, TimeoutState timeout ) { this.computer = computer; this.timeout = timeout; + this.context = new LuaContext( computer ); debug = new TimeoutDebugHandler(); // Create an environment to run in @@ -509,53 +509,6 @@ private void handleSoftAbort() throws LuaError } } - private class CobaltLuaContext implements ILuaContext - { - @Override - public long issueMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException - { - // Issue command - final long taskID = MainThread.getUniqueTaskID(); - final Runnable iTask = () -> { - try - { - Object[] results = task.execute(); - if( results != null ) - { - Object[] eventArguments = new Object[results.length + 2]; - eventArguments[0] = taskID; - eventArguments[1] = true; - System.arraycopy( results, 0, eventArguments, 2, results.length ); - computer.queueEvent( "task_complete", eventArguments ); - } - else - { - computer.queueEvent( "task_complete", new Object[] { taskID, true } ); - } - } - catch( LuaException e ) - { - computer.queueEvent( "task_complete", new Object[] { taskID, false, e.getMessage() } ); - } - catch( Throwable t ) - { - if( ComputerCraft.logComputerErrors ) ComputerCraft.log.error( "Error running task", t ); - computer.queueEvent( "task_complete", new Object[] { - taskID, false, "Java Exception Thrown: " + t, - } ); - } - }; - if( computer.queueMainThread( iTask ) ) - { - return taskID; - } - else - { - throw new LuaException( "Task limit exceeded" ); - } - } - } - private static final class HardAbortError extends Error { private static final long serialVersionUID = 7954092008586367501L; diff --git a/src/main/java/dan200/computercraft/core/lua/LuaContext.java b/src/main/java/dan200/computercraft/core/lua/LuaContext.java new file mode 100644 index 000000000..717893118 --- /dev/null +++ b/src/main/java/dan200/computercraft/core/lua/LuaContext.java @@ -0,0 +1,64 @@ +package dan200.computercraft.core.lua; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.ILuaTask; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.core.computer.Computer; +import dan200.computercraft.core.computer.MainThread; + +import javax.annotation.Nonnull; + +public class LuaContext implements ILuaContext +{ + private final Computer computer; + + public LuaContext( Computer computer ) + { + this.computer = computer; + } + + @Override + public long issueMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException + { + // Issue command + final long taskID = MainThread.getUniqueTaskID(); + final Runnable iTask = () -> { + try + { + Object[] results = task.execute(); + if( results != null ) + { + Object[] eventArguments = new Object[results.length + 2]; + eventArguments[0] = taskID; + eventArguments[1] = true; + System.arraycopy( results, 0, eventArguments, 2, results.length ); + computer.queueEvent( "task_complete", eventArguments ); + } + else + { + computer.queueEvent( "task_complete", new Object[] { taskID, true } ); + } + } + catch( LuaException e ) + { + computer.queueEvent( "task_complete", new Object[] { taskID, false, e.getMessage() } ); + } + catch( Throwable t ) + { + if( ComputerCraft.logComputerErrors ) ComputerCraft.log.error( "Error running task", t ); + computer.queueEvent( "task_complete", new Object[] { + taskID, false, "Java Exception Thrown: " + t, + } ); + } + }; + if( computer.queueMainThread( iTask ) ) + { + return taskID; + } + else + { + throw new LuaException( "Task limit exceeded" ); + } + } +} diff --git a/src/main/java/dan200/computercraft/core/lua/waluaigi/LuaState.java b/src/main/java/dan200/computercraft/core/lua/waluaigi/LuaState.java new file mode 100644 index 000000000..29cb0e2cd --- /dev/null +++ b/src/main/java/dan200/computercraft/core/lua/waluaigi/LuaState.java @@ -0,0 +1,457 @@ +package dan200.computercraft.core.lua.waluaigi; + +import cc.tweaked.waluaigi.*; +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.TimeoutState; +import dan200.computercraft.core.lua.LuaContext; +import dan200.computercraft.core.lua.MachineResult; +import it.unimi.dsi.fastutil.ints.IntArrayFIFOQueue; +import it.unimi.dsi.fastutil.ints.IntPriorityQueue; + +import javax.annotation.Nullable; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; + +class LuaState implements ExecutionEnvironment +{ + private static final int INITIAL_MEMORY = 1 * 1024 * 1024; + private static final int STRING_BUFFER_SIZE = 1024; + private static final IDynamicLuaObject[] EMPTY_OBJECTS = new IDynamicLuaObject[0]; + + private final ILuaContext context; + private final Lua lua; + private final Memory memory; + + private final TimeoutState timeout; + private int insnCount = 0; + + private IDynamicLuaObject[] objects = EMPTY_OBJECTS; + private final IntPriorityQueue freeSlots = new IntArrayFIFOQueue( 0 ); + private int nextSlot = 0; + + private final int mainState; + private final int childState; + + private final int intBox; + private final int stringBuffer; + + private String eventFilter; + + public LuaState( int maxMemory, Computer computer, TimeoutState timeout ) + { + this.context = new LuaContext( computer ); + this.timeout = timeout; + this.memory = new Memory( INITIAL_MEMORY, maxMemory ); + this.lua = LuaFactory.create( this, memory ); + + this.intBox = lua.malloc( 4 ); + this.stringBuffer = lua.malloc( STRING_BUFFER_SIZE ); + this.mainState = lua.waluaigi_new_state(); + this.childState = lua.lua_newthread( mainState ); + } + + public boolean isOpen() + { + return mainState != 0 && childState != 0 && intBox != 0 && stringBuffer != 0; + } + + public int allocate( IDynamicLuaObject object ) + { + if( !freeSlots.isEmpty() ) + { + int slot = freeSlots.dequeueInt(); + objects[slot] = object; + return slot; + } + + if( nextSlot >= objects.length ) + { + objects = objects == EMPTY_OBJECTS ? new IDynamicLuaObject[16] : Arrays.copyOf( objects, objects.length * 2 ); + } + + int slot = nextSlot++; + objects[slot] = object; + return slot; + } + + @Override + public void free( int object ) + { + if( object < 0 || object >= objects.length || objects[object] == null ) return; + objects[object] = null; + freeSlots.enqueue( object ); + } + + @Override + public int invoke( int luaState, int object, int method ) + { + IDynamicLuaObject luaObject; + if( object < 0 || object >= objects.length || (luaObject = objects[object]) == null ) + { + pushString( luaState, "Invalid object" ); + lua.lua_error( luaState ); + throw new UnreachableException(); + } + + int nargs = lua.lua_gettop( luaState ); + Object[] args = new Object[nargs]; + for( int i = 0; i < nargs; i++ ) args[i] = fromLua( luaState, i + 1 ); + + MethodResult result; + try + { + result = luaObject.callMethod( context, method, new ObjectArguments( args ) ); + } + catch( LuaException e ) + { + lua.luaL_where( luaState, e.getLevel() ); + pushString( luaState, e.getMessage() ); + lua.lua_concat( luaState, 2 ); + lua.lua_error( luaState ); + throw new UnreachableException(); + } + + if( result.getCallback() != null ) throw new IllegalStateException( "NYI: Yields" ); + + Object[] values = result.getResult(); + if( values == null ) return 0; + + for( Object value : values ) pushObject( luaState, value ); + return values.length; + } + + private Object fromLua( int luaState, int slot ) + { + int type = lua.lua_type( luaState, slot ); + switch( type ) + { + case LuaConstants.LUA_TNIL: + return null; + case LuaConstants.LUA_TBOOLEAN: + return lua.lua_toboolean( luaState, slot ) != 0; + case LuaConstants.LUA_TLIGHTUSERDATA: + return null; + case LuaConstants.LUA_TNUMBER: + return lua.lua_tonumberx( luaState, slot, 0 ); + case LuaConstants.LUA_TSTRING: + return getString( luaState, slot ); + case LuaConstants.LUA_TTABLE: + ComputerCraft.log.warn( "NYI: conversion from table" ); + // TODO: Conversion fom tables + return null; + case LuaConstants.LUA_TFUNCTION: + case LuaConstants.LUA_TUSERDATA: + case LuaConstants.LUA_TTHREAD: + default: + return null; + } + } + + public void addAPI( ILuaAPI api ) + { + int mainState = childState; + pushLuaObject( mainState, api, true ); + for( String name : api.getNames() ) + { + lua.lua_pushvalue( mainState, -1 ); + setShortString( name ); // TODO: Support long strings. + lua.lua_setglobal( mainState, stringBuffer ); + } + + lua.lua_settop( mainState, -2 ); + } + + public MachineResult handleEvent( @Nullable String eventName, @Nullable Object[] arguments ) + { + ComputerCraft.log.warn( "Resuming computer {} vs {}", eventName, eventFilter ); + + if( eventFilter != null && eventName != null && !eventName.equals( eventFilter ) && !eventName.equals( "terminate" ) ) + { + return MachineResult.OK; + } + + // If the soft abort has been cleared then we can reset our flag. + timeout.refresh(); + + int args = 0; + if( eventName != null ) + { + args++; + pushString( childState, eventName ); + } + if( arguments != null ) + { + args += arguments.length; + for( Object object : arguments ) pushObject( childState, object ); + } + + int result = lua.lua_resume( childState, 0, args, intBox ); + switch( result ) + { + case LuaConstants.LUA_OK: + { + ComputerCraft.log.warn( "Coroutine finished for no reason!" ); + return MachineResult.GENERIC_ERROR; + } + case LuaConstants.LUA_YIELD: + { + int nargs = memory.getInt( intBox ); + eventFilter = nargs > 0 && lua.lua_type( childState, 1 ) == LuaConstants.LUA_TSTRING + ? getString( childState, 1 ) + : null; + lua.lua_settop( childState, -nargs - 1 ); + ComputerCraft.log.warn( "Computer yielded OK with {}", eventFilter ); + return MachineResult.OK; + } + default: + { + String error = getString( childState, -1 ); + ComputerCraft.log.warn( "Top level coroutine errored ({})", error ); + return MachineResult.error( error ); + } + } + } + + private boolean pushDynamicObject( int luaState, IDynamicLuaObject object ) + { + lua.waluaigi_pushobject( luaState, allocate( object ) ); + + String[] methods = Objects.requireNonNull( object.getMethodNames(), "Methods cannot be null" ); + for( int i = 0; i < methods.length; i++ ) + { + pushString( luaState, methods[i] ); + lua.lua_pushvalue( luaState, -2 ); + lua.waluaigi_pushfunction( luaState, i ); + lua.lua_rawset( luaState, -4 ); + } + + lua.lua_settop( luaState, -2 ); + + return methods.length > 0; + } + + private void pushLuaObject( int luaState, Object object, boolean force ) + { + boolean hasMethods = false; + + lua.lua_createtable( luaState, 0, 0 ); + + if( object instanceof IDynamicLuaObject ) + { + hasMethods = pushDynamicObject( luaState, (IDynamicLuaObject) object ); + } + + hasMethods |= pushDynamicObject( luaState, new WrappedLuaObject( object, LuaMethod.GENERATOR.getMethods( object.getClass() ) ) ); + if( object instanceof ObjectSource ) + { + for( Object extra : ((ObjectSource) object).getExtra() ) + { + hasMethods |= pushDynamicObject( luaState, new WrappedLuaObject( extra, LuaMethod.GENERATOR.getMethods( extra.getClass() ) ) ); + } + } + + if( hasMethods ) return; + + ComputerCraft.log.warn( "Received unknown type '{}'.", object.getClass().getName() ); + if( !force ) + { + lua.lua_settop( luaState, -2 ); + lua.lua_pushnil( luaState ); + } + } + + private void pushObject( int luaState, Object object ) + { + if( object == null ) + { + lua.lua_pushnil( luaState ); + } + else if( object instanceof Long || object instanceof Integer ) + { + lua.lua_pushinteger( luaState, ((Number) object).longValue() ); + } + else if( object instanceof Number ) + { + lua.lua_pushnumber( luaState, ((Number) object).doubleValue() ); + } + else if( object instanceof Boolean ) + { + lua.lua_pushboolean( luaState, (Boolean) object ? 1 : 0 ); + } + else if( object instanceof String ) + { + pushString( luaState, (String) object ); + } + else if( object instanceof byte[] ) + { + byte[] b = (byte[]) object; + pushString( luaState, b, 0, b.length ); + } + else if( object instanceof ByteBuffer ) + { + pushString( luaState, (ByteBuffer) object ); + } + else if( object instanceof IDynamicLuaObject ) + { + pushLuaObject( luaState, object, true ); + } + else if( object instanceof Map ) + { + Map map = (Map) object; + lua.lua_createtable( luaState, 0, map.size() ); + + for( Map.Entry pair : map.entrySet() ) + { + pushObject( luaState, pair.getKey() ); + pushObject( luaState, pair.getValue() ); + lua.lua_rawset( luaState, -3 ); + } + } + else if( object instanceof Collection ) + { + Collection collection = (Collection) object; + lua.lua_createtable( luaState, collection.size(), 0 ); + + int i = 0; + for( Object obj : collection ) + { + pushObject( luaState, obj ); + lua.lua_rawseti( luaState, -2, ++i ); + } + } + else if( object instanceof Object[] ) + { + Object[] collection = (Object[]) object; + lua.lua_createtable( luaState, collection.length, 0 ); + + for( int i = 0; i < collection.length; i++ ) + { + pushObject( luaState, collection[i] ); + lua.lua_rawseti( luaState, -2, i + 1 ); + } + } + else + { + pushLuaObject( luaState, object, false ); + } + } + + @Override + public TimeoutMode getTimeout() + { + if( timeout.isHardAborted() ) return TimeoutMode.HALT; + + if( (insnCount = (insnCount + 1) & 127) == 0 ) + { + timeout.refresh(); + if( timeout.isSoftAborted() ) return TimeoutMode.ERROR; + if( timeout.isPaused() ) return TimeoutMode.PAUSE; + } + + return TimeoutMode.OK; + } + + public MachineResult loadString( ByteBuffer contents, String name ) + { + int contentsLen = contents.remaining(); + int contentsLoc = lua.malloc( contentsLen ); + memory.put( contentsLoc, contents ); + + setShortString( name ); + + int result = lua.luaL_loadbufferx( childState, contentsLoc, contentsLen, stringBuffer, 0 ); + lua.free( contentsLoc ); + + return result == 0 ? MachineResult.OK : MachineResult.error( "Cannot parse bios.lua: " + getString( childState, 1 ) ); + } + + private void setShortString( String string ) + { + int length = string.length(); + if( length >= STRING_BUFFER_SIZE ) throw new IllegalArgumentException( "String must be <1024 characters" ); + memory.putString( stringBuffer, string ); + memory.put( stringBuffer + string.length(), (byte) 0 ); + } + + public void pushString( int luaState, String string ) + { + int length = string.length(); + if( length <= STRING_BUFFER_SIZE ) + { + memory.putString( stringBuffer, string ); + lua.lua_pushlstring( luaState, stringBuffer, length ); + } + else + { + int position = lua.malloc( length + 1 ); + if( position == 0 ) throw new OutOfMemoryException( "Cannot allocate string" ); + + memory.putString( position, string ); + lua.lua_pushlstring( luaState, position, length ); + lua.free( position ); + } + } + + public void pushString( int luaState, ByteBuffer buffer ) + { + int length = buffer.remaining(); + if( length <= STRING_BUFFER_SIZE ) + { + memory.put( stringBuffer, buffer ); + lua.lua_pushlstring( luaState, stringBuffer, length ); + } + else + { + int position = lua.malloc( length ); + if( position == 0 ) throw new OutOfMemoryException( "Cannot allocate string" ); + + memory.put( position, buffer ); + lua.lua_pushlstring( luaState, position, length ); + lua.free( position ); + } + } + + public void pushString( int luaState, byte[] bytes, int offset, int length ) + { + if( length <= STRING_BUFFER_SIZE ) + { + memory.put( stringBuffer, bytes, offset, length ); + lua.lua_pushlstring( luaState, stringBuffer, length ); + } + else + { + int position = lua.malloc( length ); + if( position == 0 ) throw new OutOfMemoryException( "Cannot allocate string" ); + + memory.put( position, bytes, offset, length ); + lua.lua_pushlstring( luaState, position, length ); + lua.free( position ); + } + } + + public String getString( int luaState, int slot ) + { + int stringPos = lua.luaL_checklstring( luaState, slot, intBox ); + if( stringPos == 0 ) throw new NullPointerException( "luaL_checklstring returned 0" ); + return memory.getString( stringPos, memory.getInt( intBox ) ); + } + + public ByteBuffer getStringAsBuffer( int luaState, int slot ) + { + int stringPos = lua.luaL_checklstring( luaState, slot, intBox ); + if( stringPos == 0 ) throw new NullPointerException( "luaL_checklstring returned 0" ); + + int length = memory.getInt( intBox ); + ByteBuffer buffer = ByteBuffer.allocate( length ); + memory.get( stringPos, buffer ); + buffer.flip(); + return buffer; + } +} diff --git a/src/main/java/dan200/computercraft/core/lua/waluaigi/UnreachableException.java b/src/main/java/dan200/computercraft/core/lua/waluaigi/UnreachableException.java new file mode 100644 index 000000000..a89af1ec4 --- /dev/null +++ b/src/main/java/dan200/computercraft/core/lua/waluaigi/UnreachableException.java @@ -0,0 +1,9 @@ +package dan200.computercraft.core.lua.waluaigi; + +class UnreachableException extends IllegalStateException +{ + public UnreachableException() + { + super( "Unreachable code" ); + } +} diff --git a/src/main/java/dan200/computercraft/core/lua/waluaigi/WaluaigiLuaMachine.java b/src/main/java/dan200/computercraft/core/lua/waluaigi/WaluaigiLuaMachine.java new file mode 100644 index 000000000..f3b48e487 --- /dev/null +++ b/src/main/java/dan200/computercraft/core/lua/waluaigi/WaluaigiLuaMachine.java @@ -0,0 +1,70 @@ +package dan200.computercraft.core.lua.waluaigi; + +import com.google.common.io.ByteStreams; +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.lua.ILuaAPI; +import dan200.computercraft.core.computer.Computer; +import dan200.computercraft.core.computer.TimeoutState; +import dan200.computercraft.core.lua.ILuaMachine; +import dan200.computercraft.core.lua.MachineResult; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; + +public class WaluaigiLuaMachine implements ILuaMachine +{ + private final Computer computer; + private final TimeoutState timeout; + private LuaState state; + + public WaluaigiLuaMachine( Computer computer, TimeoutState timeout ) + { + this.computer = computer; + this.timeout = timeout; + this.state = new LuaState( 128 * 1024 * 1024, computer, timeout ); + } + + @Override + public void addAPI( @Nonnull ILuaAPI api ) + { + if( state != null && state.isOpen() ) state.addAPI( api ); + } + + @Override + public MachineResult loadBios( @Nonnull InputStream bios ) + { + if( state == null || !state.isOpen() ) return MachineResult.error( "Could not create Lua state" ); + + byte[] bytes; + try + { + bytes = ByteStreams.toByteArray( bios ); + } + catch( IOException e ) + { + ComputerCraft.log.error( "Error reading bios", e ); + return MachineResult.error( "Could not load bios" ); + } + + MachineResult load = state.loadString( ByteBuffer.wrap( bytes ), "@bios.lua" ); + if( load.isError() ) return load; + + return MachineResult.OK; + } + + @Override + public MachineResult handleEvent( @Nullable String eventName, @Nullable Object[] arguments ) + { + if( state == null || !state.isOpen() ) return MachineResult.error( "Machine is not open" ); + return state.handleEvent( eventName, arguments ); + } + + @Override + public void close() + { + state = null; + } +} diff --git a/src/main/java/dan200/computercraft/core/lua/waluaigi/WrappedLuaObject.java b/src/main/java/dan200/computercraft/core/lua/waluaigi/WrappedLuaObject.java new file mode 100644 index 000000000..1a91c61b5 --- /dev/null +++ b/src/main/java/dan200/computercraft/core/lua/waluaigi/WrappedLuaObject.java @@ -0,0 +1,39 @@ +package dan200.computercraft.core.lua.waluaigi; + +import dan200.computercraft.api.lua.*; +import dan200.computercraft.core.asm.LuaMethod; +import dan200.computercraft.core.asm.NamedMethod; + +import javax.annotation.Nonnull; +import java.util.List; + +class WrappedLuaObject implements IDynamicLuaObject +{ + private final Object object; + private final List> methods; + + public WrappedLuaObject( Object object, List> methods ) + { + this.object = object; + this.methods = methods; + } + + @Nonnull + @Override + public String[] getMethodNames() + { + // TODO: Make this less allocation happy. + String[] methods = new String[this.methods.size()]; + int i = 0; + for( NamedMethod method : this.methods ) methods[i++] = method.getName(); + return methods; + } + + @Nonnull + @Override + public MethodResult callMethod( @Nonnull ILuaContext context, int method, @Nonnull IArguments arguments ) throws LuaException + { + if( method < 0 || method >= methods.size() ) throw new LuaException( "Unknown method" ); + return methods.get( method ).getMethod().apply( object, context, arguments ); + } +} diff --git a/src/main/resources/data/computercraft/lua/bios.lua b/src/main/resources/data/computercraft/lua/bios.lua index f0c364994..7058914ea 100644 --- a/src/main/resources/data/computercraft/lua/bios.lua +++ b/src/main/resources/data/computercraft/lua/bios.lua @@ -7,7 +7,7 @@ local expect do local h = fs.open("rom/modules/main/cc/expect.lua", "r") - local f, err = loadstring(h.readAll(), "@expect.lua") + local f, err = load(h.readAll(), "@expect.lua") h.close() if not f then error(err) end