1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-06-25 06:33:23 +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:
SquidDev 2019-03-10 12:24:55 +00:00
parent 7b5a918941
commit 5b942ff9c1
6 changed files with 216 additions and 202 deletions

View File

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

View File

@ -21,7 +21,6 @@
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.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 CobaltLuaMachine( Computer computer, TimeoutState timeout )
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 @@ private LuaTable wrapLuaObject( ILuaObject object )
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 @@ private static Object[] toObjects( Varargs values, int startIdx )
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 @@ 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
{
// 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;

View File

@ -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,

View File

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

View File

@ -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,12 +150,14 @@ 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
-- And verify all pairs in right are present in left
for k in pairs(right) do
if left[k] == nil then return false 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
@ -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

View 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)