1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-06-26 07:03:22 +00:00
CC-Tweaked/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java
SquidDev 73873eb8cb Change timeout system to occur on instructions instead of API calls
This means loops which do not touch CC specific methods will still
produce an error, rather than terminating the computer.
2017-11-14 18:41:00 +00:00

645 lines
23 KiB
Java

/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2017. 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.ILuaContext;
import dan200.computercraft.api.lua.ILuaObject;
import dan200.computercraft.api.lua.ILuaTask;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.apis.ILuaAPI;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.ITask;
import dan200.computercraft.core.computer.MainThread;
import org.squiddev.cobalt.*;
import org.squiddev.cobalt.compiler.CompileException;
import org.squiddev.cobalt.compiler.LoadState;
import org.squiddev.cobalt.debug.DebugFrame;
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.AbstractResourceManipulator;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
import static org.squiddev.cobalt.Constants.NONE;
import static org.squiddev.cobalt.ValueFactory.valueOf;
import static org.squiddev.cobalt.ValueFactory.varargsOf;
public class CobaltLuaMachine implements ILuaMachine
{
private final Computer m_computer;
private final LuaState m_state;
private final LuaTable m_globals;
private LuaThread m_mainRoutine;
private String m_eventFilter;
private String m_softAbortMessage;
private String m_hardAbortMessage;
public CobaltLuaMachine( Computer computer )
{
m_computer = computer;
// Create an environment to run in
final LuaState state = this.m_state = new LuaState( new AbstractResourceManipulator()
{
@Override
public InputStream findResource( String filename )
{
return null;
}
} );
state.debug = new DebugHandler( state )
{
private int count = 0;
private boolean hasSoftAbort;
@Override
public void onInstruction( DebugState ds, DebugFrame di, int pc, Varargs extras, int top ) throws LuaError
{
int count = ++this.count;
if( count > 100000 )
{
if( m_hardAbortMessage != null ) LuaThread.yield( state, NONE );
this.count = 0;
}
else
{
handleSoftAbort();
}
super.onInstruction( ds, di, pc, extras, top );
}
@Override
public void poll() throws LuaError
{
if( m_hardAbortMessage != null ) LuaThread.yield( state, NONE );
handleSoftAbort();
}
private void handleSoftAbort() throws LuaError {
// If the soft abort has been cleared then we can reset our flags and continue.
String message = m_softAbortMessage;
if (message == null) {
hasSoftAbort = false;
return;
}
if (hasSoftAbort && m_hardAbortMessage == null) {
// If we have fired our soft abort, but we haven't been hard aborted then everything is OK.
return;
}
hasSoftAbort = true;
throw new LuaError(message);
}
};
m_globals = new LuaTable();
state.setupThread( m_globals );
// Add basic libraries
m_globals.load( state, new BaseLib() );
m_globals.load( state, new TableLib() );
m_globals.load( state, new StringLib() );
m_globals.load( state, new MathLib() );
m_globals.load( state, new CoroutineLib() );
// Remove globals we don't want to expose
m_globals.rawset( "collectgarbage", Constants.NIL );
m_globals.rawset( "dofile", Constants.NIL );
m_globals.rawset( "loadfile", Constants.NIL );
m_globals.rawset( "print", Constants.NIL );
// Add version globals
m_globals.rawset( "_VERSION", valueOf( "Lua 5.1" ) );
m_globals.rawset( "_HOST", valueOf( computer.getAPIEnvironment().getComputerEnvironment().getHostString() ) );
m_globals.rawset( "_CC_DEFAULT_SETTINGS", valueOf( ComputerCraft.default_computer_settings ) );
if( ComputerCraft.disable_lua51_features )
{
m_globals.rawset( "_CC_DISABLE_LUA51_FEATURES", Constants.TRUE );
}
// Our main function will go here
m_mainRoutine = null;
m_eventFilter = null;
m_softAbortMessage = null;
m_hardAbortMessage = null;
}
@Override
public void addAPI( ILuaAPI api )
{
// Add the methods of an API to the global table
LuaTable table = wrapLuaObject( api );
String[] names = api.getNames();
for( String name : names )
{
m_globals.rawset( name, table );
}
}
@Override
public void loadBios( InputStream bios )
{
// Begin executing a file (ie, the bios)
if( m_mainRoutine != null )
{
return;
}
try
{
LuaFunction value = LoadState.load( m_state, bios, "@bios.lua", m_globals );
m_mainRoutine = new LuaThread( m_state, value, m_globals );
}
catch( CompileException e )
{
if( m_mainRoutine != null )
{
m_mainRoutine.abandon();
m_mainRoutine = null;
}
}
catch( IOException e )
{
ComputerCraft.log.warn( "Could not load bios.lua ", e );
if( m_mainRoutine != null )
{
m_mainRoutine.abandon();
m_mainRoutine = null;
}
}
}
@Override
public void handleEvent( String eventName, Object[] arguments )
{
if( m_mainRoutine == null )
{
return;
}
if( m_eventFilter != null && eventName != null && !eventName.equals( m_eventFilter ) && !eventName.equals( "terminate" ) )
{
return;
}
try
{
Varargs resumeArgs = Constants.NONE;
if( eventName != null )
{
resumeArgs = varargsOf( valueOf( eventName ), toValues( arguments ) );
}
Varargs results = m_mainRoutine.resume( resumeArgs );
if( m_hardAbortMessage != null )
{
throw new LuaError( m_hardAbortMessage );
}
else if( !results.first().checkBoolean() )
{
throw new LuaError( results.arg( 2 ).checkString() );
}
else
{
LuaValue filter = results.arg( 2 );
if( filter.isString() )
{
m_eventFilter = filter.toString();
}
else
{
m_eventFilter = null;
}
}
LuaThread mainThread = m_mainRoutine;
if( mainThread.getStatus().equals( "dead" ) )
{
m_mainRoutine = null;
}
}
catch( LuaError e )
{
m_mainRoutine.abandon();
m_mainRoutine = null;
}
finally
{
m_softAbortMessage = null;
m_hardAbortMessage = null;
}
}
@Override
public void softAbort( String abortMessage )
{
m_softAbortMessage = abortMessage;
}
@Override
public void hardAbort( String abortMessage )
{
m_softAbortMessage = abortMessage;
m_hardAbortMessage = abortMessage;
}
@Override
public boolean saveState( OutputStream output )
{
return false;
}
@Override
public boolean restoreState( InputStream input )
{
return false;
}
@Override
public boolean isFinished()
{
return (m_mainRoutine == null);
}
@Override
public void unload()
{
if( m_mainRoutine != null )
{
LuaThread mainThread = m_mainRoutine;
mainThread.abandon();
m_mainRoutine = null;
}
}
private LuaTable wrapLuaObject( ILuaObject object )
{
LuaTable table = new LuaTable();
String[] methods = object.getMethodNames();
for( int i = 0; i < methods.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( new ILuaContext()
{
@Nonnull
@Override
public Object[] pullEvent( String filter ) throws LuaException, InterruptedException
{
Object[] results = pullEventRaw( filter );
if( results.length >= 1 && results[ 0 ].equals( "terminate" ) )
{
throw new LuaException( "Terminated", 0 );
}
return results;
}
@Nonnull
@Override
public Object[] pullEventRaw( String filter ) throws InterruptedException
{
return yield( new Object[] { filter } );
}
@Nonnull
@Override
public Object[] yield( Object[] yieldArgs ) throws InterruptedException
{
try
{
Varargs results = LuaThread.yield( state, toValues( yieldArgs ) );
return toObjects( results, 1 );
}
catch( OrphanedThread e )
{
throw new InterruptedException();
}
catch( Throwable e )
{
throw new RuntimeException( e );
}
}
@Override
public long issueMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException
{
// Issue command
final long taskID = MainThread.getUniqueTaskID();
final ITask iTask = new ITask()
{
@Override
public Computer getOwner()
{
return m_computer;
}
@Override
public void execute()
{
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 );
m_computer.queueEvent( "task_complete", eventArguments );
}
else
{
m_computer.queueEvent( "task_complete", new Object[] { taskID, true } );
}
}
catch( LuaException e )
{
m_computer.queueEvent( "task_complete", new Object[] {
taskID, false, e.getMessage()
} );
}
catch( Throwable t )
{
if( ComputerCraft.logPeripheralErrors )
{
ComputerCraft.log.error( "Error running task", t );
}
m_computer.queueEvent( "task_complete", new Object[] {
taskID, false, "Java Exception Thrown: " + t.toString()
} );
}
}
};
if( MainThread.queueTask( iTask ) )
{
return taskID;
}
else
{
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();
}
}
}
}
}
}
}, method, arguments );
}
catch( InterruptedException e )
{
throw new OrphanedThread();
}
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.toString(), 0 );
}
return toValues( results );
}
} );
}
}
return table;
}
private LuaValue toValue( Object object, Map<Object, LuaValue> values )
{
if( object == null )
{
return Constants.NIL;
}
else if( object instanceof Number )
{
double d = ((Number) object).doubleValue();
return valueOf( d );
}
else if( object instanceof Boolean )
{
return valueOf( (Boolean) object );
}
else if( object instanceof String )
{
String s = object.toString();
return valueOf( s );
}
else if( object instanceof byte[] )
{
byte[] b = (byte[]) object;
return valueOf( Arrays.copyOf( b, b.length ) );
}
else if( object instanceof Map )
{
// Table:
// Start remembering stuff
if( values == null )
{
values = new IdentityHashMap<>();
}
else if( values.containsKey( object ) )
{
return values.get( object );
}
LuaTable table = new LuaTable();
values.put( object, table );
// Convert all keys
for( Map.Entry<?, ?> pair : ((Map<?, ?>) object).entrySet() )
{
LuaValue key = toValue( pair.getKey(), values );
LuaValue value = toValue( pair.getValue(), values );
if( !key.isNil() && !value.isNil() )
{
table.rawset( key, value );
}
}
return table;
}
else if( object instanceof ILuaObject )
{
return wrapLuaObject( (ILuaObject) object );
}
else
{
return Constants.NIL;
}
}
private Varargs toValues( Object[] objects )
{
if( objects == null || objects.length == 0 )
{
return Constants.NONE;
}
LuaValue[] values = new LuaValue[ objects.length ];
for( int i = 0; i < values.length; ++i )
{
Object object = objects[ i ];
values[ i ] = toValue( object, null );
}
return varargsOf( values );
}
private static Object toObject( LuaValue value, Map<LuaValue, Object> objects )
{
switch( value.type() )
{
case Constants.TNIL:
case Constants.TNONE:
{
return null;
}
case Constants.TINT:
case Constants.TNUMBER:
{
return value.toDouble();
}
case Constants.TBOOLEAN:
{
return value.toBoolean();
}
case Constants.TSTRING:
{
return value.toString();
}
case Constants.TTABLE:
{
// Table:
// Start remembering stuff
if( objects == null )
{
objects = new IdentityHashMap<>();
}
else if( objects.containsKey( value ) )
{
return objects.get( value );
}
Map<Object, Object> table = new HashMap<>();
objects.put( value, table );
LuaTable luaTable = (LuaTable) value;
// Convert all keys
LuaValue k = Constants.NIL;
while( true )
{
Varargs keyValue;
try
{
keyValue = luaTable.next( k );
}
catch( LuaError luaError )
{
break;
}
k = keyValue.first();
if( k.isNil() )
{
break;
}
LuaValue v = keyValue.arg( 2 );
Object keyObject = toObject( k, objects );
Object valueObject = toObject( v, objects );
if( keyObject != null && valueObject != null )
{
table.put( keyObject, valueObject );
}
}
return table;
}
default:
{
return null;
}
}
}
private static Object[] toObjects( Varargs values, int startIdx )
{
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 );
}
return objects;
}
}