mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2024-12-12 11:10:29 +00:00
Some changes to Lua machines and string loading
- Share the ILuaContext across all method calls, as well as shifting it into an anonymous class. - Move the load/loadstring prefixing into bios.lua - Be less militant in prefixing chunk names: - load will no longer do any auto-prefixing. - loadstring will not prefix when there no chunk name is supplied. Before we would do `"=" .. supplied_program`, which made no sense.
This commit is contained in:
parent
7b5a918941
commit
5b942ff9c1
@ -32,7 +32,11 @@ public interface ILuaContext
|
||||
* intercepted, or the computer will leak memory and end up in a broken state.
|
||||
*/
|
||||
@Nonnull
|
||||
Object[] pullEvent( @Nullable String filter ) throws LuaException, InterruptedException;
|
||||
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
|
||||
@ -47,7 +51,9 @@ public interface ILuaContext
|
||||
* @see #pullEvent(String)
|
||||
*/
|
||||
@Nonnull
|
||||
Object[] pullEventRaw( @Nullable String filter ) throws InterruptedException;
|
||||
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
|
||||
|
@ -21,7 +21,6 @@ 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.LibFunction;
|
||||
import org.squiddev.cobalt.function.LuaFunction;
|
||||
import org.squiddev.cobalt.function.VarArgFunction;
|
||||
import org.squiddev.cobalt.lib.*;
|
||||
@ -37,7 +36,6 @@ import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.squiddev.cobalt.Constants.NONE;
|
||||
import static org.squiddev.cobalt.ValueFactory.valueOf;
|
||||
import static org.squiddev.cobalt.ValueFactory.varargsOf;
|
||||
import static org.squiddev.cobalt.debug.DebugFrame.FLAG_HOOKED;
|
||||
@ -55,6 +53,7 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
private final Computer m_computer;
|
||||
private final TimeoutState timeout;
|
||||
private final TimeoutDebugHandler debug;
|
||||
private final ILuaContext context = new CobaltLuaContext();
|
||||
|
||||
private LuaState m_state;
|
||||
private LuaTable m_globals;
|
||||
@ -99,10 +98,6 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
m_globals.load( state, new Bit32Lib() );
|
||||
if( ComputerCraft.debug_enable ) m_globals.load( state, new DebugLib() );
|
||||
|
||||
// Register custom load/loadstring provider which automatically adds prefixes.
|
||||
m_globals.rawset( "load", new PrefixWrapperFunction( m_globals.rawget( "load" ), 0 ) );
|
||||
m_globals.rawset( "loadstring", new PrefixWrapperFunction( m_globals.rawget( "loadstring" ), 1 ) );
|
||||
|
||||
// Remove globals we don't want to expose
|
||||
m_globals.rawset( "collectgarbage", Constants.NIL );
|
||||
m_globals.rawset( "dofile", Constants.NIL );
|
||||
@ -238,148 +233,13 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
table.rawset( methodName, new VarArgFunction()
|
||||
{
|
||||
@Override
|
||||
public Varargs invoke( final LuaState state, Varargs _args ) throws LuaError
|
||||
public Varargs invoke( final LuaState state, Varargs args ) throws LuaError
|
||||
{
|
||||
Object[] arguments = toObjects( _args, 1 );
|
||||
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.yieldBlocking( state, toValues( yieldArgs ) );
|
||||
return toObjects( results, 1 );
|
||||
}
|
||||
catch( LuaError e )
|
||||
{
|
||||
throw new IllegalStateException( e );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long issueMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException
|
||||
{
|
||||
// Issue command
|
||||
final long taskID = MainThread.getUniqueTaskID();
|
||||
final ITask iTask = new ITask()
|
||||
{
|
||||
@Nonnull
|
||||
@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 );
|
||||
results = apiObject.callMethod( context, method, arguments );
|
||||
}
|
||||
catch( InterruptedException e )
|
||||
{
|
||||
@ -571,54 +431,6 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
return objects;
|
||||
}
|
||||
|
||||
private static class PrefixWrapperFunction extends VarArgFunction
|
||||
{
|
||||
private static final LuaString FUNCTION_STR = valueOf( "function" );
|
||||
private static final LuaString EQ_STR = valueOf( "=" );
|
||||
|
||||
private final LibFunction underlying;
|
||||
|
||||
public PrefixWrapperFunction( LuaValue wrap, int opcode )
|
||||
{
|
||||
LibFunction underlying = (LibFunction) wrap;
|
||||
|
||||
this.underlying = underlying;
|
||||
this.opcode = opcode;
|
||||
this.name = underlying.debugName();
|
||||
this.env = underlying.getfenv();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Varargs invoke( LuaState state, Varargs args ) throws LuaError, UnwindThrowable
|
||||
{
|
||||
switch( opcode )
|
||||
{
|
||||
case 0: // "load", // ( func [,chunkname] ) -> chunk | nil, msg
|
||||
{
|
||||
LuaValue func = args.arg( 1 ).checkFunction();
|
||||
LuaString chunkname = args.arg( 2 ).optLuaString( FUNCTION_STR );
|
||||
if( !chunkname.startsWith( '@' ) && !chunkname.startsWith( '=' ) )
|
||||
{
|
||||
chunkname = OperationHelper.concat( EQ_STR, chunkname );
|
||||
}
|
||||
return underlying.invoke( state, varargsOf( func, chunkname ) );
|
||||
}
|
||||
case 1: // "loadstring", // ( string [,chunkname] ) -> chunk | nil, msg
|
||||
{
|
||||
LuaString script = args.arg( 1 ).checkLuaString();
|
||||
LuaString chunkname = args.arg( 2 ).optLuaString( script );
|
||||
if( !chunkname.startsWith( '@' ) && !chunkname.startsWith( '=' ) )
|
||||
{
|
||||
chunkname = OperationHelper.concat( EQ_STR, chunkname );
|
||||
}
|
||||
return underlying.invoke( state, varargsOf( script, chunkname ) );
|
||||
}
|
||||
}
|
||||
|
||||
return NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link DebugHandler} which observes the {@link TimeoutState} and responds accordingly.
|
||||
*/
|
||||
@ -700,6 +512,126 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
// Issue command
|
||||
final long taskID = MainThread.getUniqueTaskID();
|
||||
final ITask iTask = new ITask()
|
||||
{
|
||||
@Nonnull
|
||||
@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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static class HardAbortError extends Error
|
||||
{
|
||||
private static final long serialVersionUID = 7954092008586367501L;
|
||||
|
@ -2,9 +2,23 @@
|
||||
local nativegetfenv = getfenv
|
||||
if _VERSION == "Lua 5.1" then
|
||||
-- If we're on Lua 5.1, install parts of the Lua 5.2/5.3 API so that programs can be written against it
|
||||
local type = type
|
||||
local nativeload = load
|
||||
local nativeloadstring = loadstring
|
||||
local nativesetfenv = setfenv
|
||||
|
||||
--- Historically load/loadstring would handle the chunk name as if it has
|
||||
-- been prefixed with "=". We emulate that behaviour here.
|
||||
local function prefix(chunkname)
|
||||
if type(chunkname) ~= "string" then return chunkname end
|
||||
local head = chunkname:sub(1, 1)
|
||||
if head == "=" or head == "@" then
|
||||
return chunkname
|
||||
else
|
||||
return "=" .. chunkname
|
||||
end
|
||||
end
|
||||
|
||||
function load( x, name, mode, env )
|
||||
if type( x ) ~= "string" and type( x ) ~= "function" then
|
||||
error( "bad argument #1 (expected string or function, got " .. type( x ) .. ")", 2 )
|
||||
@ -62,7 +76,10 @@ if _VERSION == "Lua 5.1" then
|
||||
math.log10 = nil
|
||||
table.maxn = nil
|
||||
else
|
||||
loadstring = function(string, chunkname) return nativeloadstring(string, prefix( chunkname ))
|
||||
|
||||
-- Inject a stub for the old bit library
|
||||
end
|
||||
_G.bit = {
|
||||
bnot = bit32.bnot,
|
||||
band = bit32.band,
|
||||
|
@ -49,8 +49,8 @@ while bRunning do
|
||||
end
|
||||
|
||||
local nForcePrint = 0
|
||||
local func, e = load( s, "lua", "t", tEnv )
|
||||
local func2, e2 = load( "return _echo("..s..");", "lua", "t", tEnv )
|
||||
local func, e = load( s, "=lua", "t", tEnv )
|
||||
local func2, e2 = load( "return _echo("..s..");", "=lua", "t", tEnv )
|
||||
if not func then
|
||||
if func2 then
|
||||
func = func2
|
||||
|
@ -137,7 +137,7 @@ function expect_mt:type(exp_type)
|
||||
return self
|
||||
end
|
||||
|
||||
local function are_same(eq, left, right)
|
||||
local function matches(eq, exact, left, right)
|
||||
if left == right then return true end
|
||||
|
||||
local ty = type(left)
|
||||
@ -150,13 +150,15 @@ local function are_same(eq, left, right)
|
||||
|
||||
-- Verify all pairs in left are equal to those in right
|
||||
for k, v in pairs(left) do
|
||||
if not are_same(eq, v, right[k]) then return false end
|
||||
if not matches(eq, exact, v, right[k]) then return false end
|
||||
end
|
||||
|
||||
if exact then
|
||||
-- And verify all pairs in right are present in left
|
||||
for k in pairs(right) do
|
||||
if left[k] == nil then return false end
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
@ -167,13 +169,26 @@ end
|
||||
-- @param value The value to check for structural equivalence
|
||||
-- @raises If they are not equivalent
|
||||
function expect_mt:same(value)
|
||||
if not are_same({}, self.value, value) then
|
||||
if not matches({}, true, self.value, value) then
|
||||
fail(("Expected %s\n but got %s"):format(format(value), format(self.value)))
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Assert that this expectation contains all fields mentioned
|
||||
-- in the provided object.
|
||||
--
|
||||
-- @param value The value to check against
|
||||
-- @raises If this does not match the provided value
|
||||
function expect_mt:matches(value)
|
||||
if not matches({}, false, value, self.value) then
|
||||
fail(("Expected %s\nto match %s"):format(format(self.value), format(value)))
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Construct a new expectation from the provided value
|
||||
--
|
||||
-- @param value The value to apply assertions to
|
||||
|
44
src/test/resources/test-rom/spec/base_spec.lua
Normal file
44
src/test/resources/test-rom/spec/base_spec.lua
Normal file
@ -0,0 +1,44 @@
|
||||
describe("The Lua base library", function()
|
||||
describe("loadfile", function()
|
||||
it("prefixes the filename with @", function()
|
||||
local info = debug.getinfo(loadfile("/rom/startup.lua"), "S")
|
||||
expect(info):matches { short_src = "startup.lua", source = "@startup.lua" }
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("loadstring", function()
|
||||
it("prefixes the chunk name with '='", function()
|
||||
local info = debug.getinfo(loadstring("return 1", "name"), "S")
|
||||
expect(info):matches { short_src = "name", source = "=name" }
|
||||
end)
|
||||
|
||||
it("does not prefix for unnamed chunks", function()
|
||||
local info = debug.getinfo(loadstring("return 1"), "S")
|
||||
expect(info):matches { short_src = '[string "return 1"]', source = "return 1", }
|
||||
end)
|
||||
|
||||
it("does not prefix when already prefixed", function()
|
||||
local info = debug.getinfo(loadstring("return 1", "@file.lua"), "S")
|
||||
expect(info):matches { short_src = "file.lua", source = "@file.lua" }
|
||||
|
||||
info = debug.getinfo(loadstring("return 1", "=file.lua"), "S")
|
||||
expect(info):matches { short_src = "file.lua", source = "=file.lua" }
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("load", function()
|
||||
local function generator(parts)
|
||||
return coroutine.wrap(function()
|
||||
for i = 1, #parts do coroutine.yield(parts[i]) end
|
||||
end)
|
||||
end
|
||||
|
||||
it("does not prefix the chunk name with '='", function()
|
||||
local info = debug.getinfo(load("return 1", "name"), "S")
|
||||
expect(info):matches { short_src = "[string \"name\"]", source = "name" }
|
||||
|
||||
info = debug.getinfo(load(generator { "return 1" }, "name"), "S")
|
||||
expect(info):matches { short_src = "[string \"name\"]", source = "name" }
|
||||
end)
|
||||
end)
|
||||
end)
|
Loading…
Reference in New Issue
Block a user