Various changes to bring pm.lua test closer to passing:

* New and complete character class support
 * string.gsub implemented
 * rawset implemented
 * lua_call added (based on lua_pcall)
 * lua_pop added
 * newCall removed; luaGetTable and luaSetTable in LFunction updated to
   use lua_call instead.
 * StandardTest changed to avoid an ArrayIndexOutOfBoundsException

Unfortunately I discovered a problem where execute() will loop around too
many times and call exec() with a call frame that has already completed.
This might be due to the lack of the "tailcalls" field in our CallInfo class
that the C version maintains?
This commit is contained in:
Ian Farmer
2007-09-24 04:35:53 +00:00
parent f801e648bb
commit ec11c472c8
8 changed files with 269 additions and 53 deletions

View File

@@ -0,0 +1,50 @@
package lua.addon.luacompat;
import lua.value.LString;
public class LBuffer {
private byte[] bytes;
private int length;
public LBuffer( int initialCapacity ) {
bytes = new byte[ initialCapacity ];
length = 0;
}
public void append( byte b ) {
ensureCapacity( length + 1 );
bytes[ length++ ] = b;
}
public void append( LString str ) {
final int alen = str.length();
ensureCapacity( length + alen );
str.copyInto( 0, bytes, length, alen );
length += alen;
}
public void setLength( int length ) {
ensureCapacity( length );
this.length = length;
}
public LString toLuaString() {
return new LString( realloc( bytes, length ) );
}
public void ensureCapacity( int minSize ) {
if ( minSize > bytes.length )
realloc( minSize );
}
private void realloc( int minSize ) {
bytes = realloc( bytes, Math.max( bytes.length * 2, minSize ) );
}
private static byte[] realloc( byte[] b, int newSize ) {
byte[] newBytes = new byte[ newSize ];
System.arraycopy( b, 0, newBytes, 0, Math.min( b.length, newSize ) );
return newBytes;
}
}

View File

@@ -67,6 +67,7 @@ public class LuaCompat extends LFunction {
"loadfile",
"tonumber",
"rawget",
"rawset",
"setfenv",
"select",
"collectgarbage",
@@ -125,17 +126,18 @@ public class LuaCompat extends LFunction {
private static final int LOADFILE = GLOBALS_BASE + 1;
private static final int TONUMBER = GLOBALS_BASE + 2;
private static final int RAWGET = GLOBALS_BASE + 3;
private static final int SETFENV = GLOBALS_BASE + 4;
private static final int SELECT = GLOBALS_BASE + 5;
private static final int COLLECTGARBAGE = GLOBALS_BASE + 6;
private static final int DOFILE = GLOBALS_BASE + 7;
private static final int LOADSTRING = GLOBALS_BASE + 8;
private static final int LOAD = GLOBALS_BASE + 9;
private static final int TOSTRING = GLOBALS_BASE + 10;
private static final int UNPACK = GLOBALS_BASE + 11;
private static final int NEXT = GLOBALS_BASE + 12;
private static final int MODULE = GLOBALS_BASE + 13;
private static final int REQUIRE = GLOBALS_BASE + 14;
private static final int RAWSET = GLOBALS_BASE + 4;
private static final int SETFENV = GLOBALS_BASE + 5;
private static final int SELECT = GLOBALS_BASE + 6;
private static final int COLLECTGARBAGE = GLOBALS_BASE + 7;
private static final int DOFILE = GLOBALS_BASE + 8;
private static final int LOADSTRING = GLOBALS_BASE + 9;
private static final int LOAD = GLOBALS_BASE + 10;
private static final int TOSTRING = GLOBALS_BASE + 11;
private static final int UNPACK = GLOBALS_BASE + 12;
private static final int NEXT = GLOBALS_BASE + 13;
private static final int MODULE = GLOBALS_BASE + 14;
private static final int REQUIRE = GLOBALS_BASE + 15;
private static final int MATH_BASE = 20;
@@ -201,7 +203,7 @@ public class LuaCompat extends LFunction {
vm.setResult( toNumber( vm ) );
break;
case RAWGET: {
LValue t = vm.getArg(0);;
LValue t = vm.getArg(0);
LValue k = vm.getArg(1);
LValue result = LNil.NIL;
if ( t instanceof LTable ) {
@@ -209,6 +211,17 @@ public class LuaCompat extends LFunction {
}
vm.setResult( result );
} break;
case RAWSET: {
LValue t = vm.getArg(0);
LValue k = vm.getArg(1);
LValue v = vm.getArg(2);
vm.setResult();
if ( t instanceof LTable ) {
( (LTable) t ).put( k, v );
} else {
vm.lua_error( "expected table" );
}
} break;
case SETFENV:
setfenv( (StackState) vm );
break;

View File

@@ -1,9 +1,13 @@
package lua.addon.luacompat;
import lua.VM;
import lua.value.LFunction;
import lua.value.LInteger;
import lua.value.LNil;
import lua.value.LNumber;
import lua.value.LString;
import lua.value.LTable;
import lua.value.LValue;
public class StrLib {
/**
@@ -185,9 +189,41 @@ public class StrLib {
* x = string.gsub("$name-$version.tar.gz", "%$(%w+)", t)
* --> x="lua-5.1.tar.gz"
*/
static void gsub( VM vm ) {
static void gsub( VM vm ) {
LString src = vm.getArgAsLuaString( 0 );
final int srclen = src.length();
LString p = vm.getArgAsLuaString( 1 );
LValue repl = vm.getArg( 2 );
int max_s = ( vm.getArgCount() > 3 ? vm.getArgAsInt( 3 ) : srclen + 1 );
final boolean anchor = p.length() > 0 && p.charAt( 0 ) == '^';
LBuffer lbuf = new LBuffer( srclen );
MatchState ms = new MatchState( vm, src, p );
int soffset = 0;
int n = 0;
while ( n < max_s ) {
ms.reset();
int res = ms.match( soffset, anchor ? 1 : 0 );
if ( res != -1 ) {
n++;
ms.add_value( lbuf, soffset, res, repl );
}
if ( res != -1 && res > soffset )
soffset = res;
else if ( soffset < srclen )
lbuf.append( (byte) src.luaByte( soffset++ ) );
else
break;
if ( anchor )
break;
}
lbuf.append( src.substring( soffset, srclen ) );
vm.setResult();
vm.push( lbuf.toLuaString() );
vm.push( new LInteger( n ) );
}
/**
* string.len (s)
*
@@ -367,6 +403,45 @@ public class StrLib {
private static final int CAP_UNFINISHED = -1;
private static final int CAP_POSITION = -2;
private static final byte MASK_ALPHA = 0x01;
private static final byte MASK_LOWERCASE = 0x02;
private static final byte MASK_UPPERCASE = 0x04;
private static final byte MASK_DIGIT = 0x08;
private static final byte MASK_PUNCT = 0x10;
private static final byte MASK_SPACE = 0x20;
private static final byte MASK_CONTROL = 0x40;
private static final byte MASK_HEXDIGIT = (byte)0x80;
private static final byte[] CHAR_TABLE;
static {
CHAR_TABLE = new byte[256];
for ( int i = 0; i < 256; ++i ) {
final char c = (char) i;
CHAR_TABLE[i] = (byte)( ( Character.isDigit( c ) ? MASK_DIGIT : 0 ) |
( Character.isLowerCase( c ) ? MASK_LOWERCASE : 0 ) |
( Character.isUpperCase( c ) ? MASK_UPPERCASE : 0 ) |
( ( c < ' ' || c == 0x7F ) ? MASK_CONTROL : 0 ) );
if ( ( c >= 'a' && c <= 'f' ) || ( c >= 'A' && c <= 'F' ) || ( c >= '0' && c <= '9' ) ) {
CHAR_TABLE[i] |= MASK_HEXDIGIT;
}
if ( ( c >= '!' && c <= '/' ) || ( c >= ':' && c <= '@' ) ) {
CHAR_TABLE[i] |= MASK_PUNCT;
}
if ( ( CHAR_TABLE[i] & ( MASK_LOWERCASE | MASK_UPPERCASE ) ) != 0 ) {
CHAR_TABLE[i] |= MASK_ALPHA;
}
}
CHAR_TABLE[' '] = MASK_SPACE;
CHAR_TABLE['\r'] |= MASK_SPACE;
CHAR_TABLE['\n'] |= MASK_SPACE;
CHAR_TABLE['\t'] |= MASK_SPACE;
CHAR_TABLE[0x0C /* '\v' */ ] |= MASK_SPACE;
CHAR_TABLE['\f'] |= MASK_SPACE;
};
private static class MatchState {
final LString s;
final LString p;
@@ -388,17 +463,71 @@ public class StrLib {
level = 0;
}
void push_captures( boolean wholeMatch, int soff, int end ) {
private void add_s( LBuffer lbuf, LString news, int soff, int e ) {
int l = news.length();
for ( int i = 0; i < l; ++i ) {
byte b = (byte) news.luaByte( i );
if ( b != L_ESC ) {
lbuf.append( (byte) b );
} else {
++i; // skip ESC
b = (byte) news.luaByte( i );
if ( !Character.isDigit( (char) b ) ) {
lbuf.append( b );
} else if ( b == '0' ) {
lbuf.append( s.substring( soff, e ) );
} else {
push_onecapture( b - '1', soff, e );
lbuf.append( vm.lua_tolvalue( -1 ).luaAsString() );
vm.lua_pop( 1 );
}
}
}
}
public void add_value( LBuffer lbuf, int soffset, int end, LValue repl ) {
if ( repl instanceof LString || repl instanceof LNumber ) {
add_s( lbuf, repl.luaAsString(), soffset, end );
return;
} else if ( repl instanceof LFunction ) {
vm.push( repl );
int n = push_captures( true, soffset, end );
vm.lua_call( n, 1 );
} else if ( repl instanceof LTable ) {
// Need to call push_onecapture here for the error checking
push_onecapture( 0, soffset, end );
LValue k = vm.lua_tolvalue( -1 );
vm.lua_pop( 1 );
((LTable) repl).luaGetTable( vm, repl, k );
} else {
vm.lua_error( "string/function/table expected" );
return;
}
repl = vm.lua_tolvalue( -1 );
if ( !repl.luaAsBoolean() ) {
repl = s.substring( soffset, end );
} else if ( ! ( repl instanceof LString || repl instanceof LNumber ) ) {
vm.lua_error( "invalid replacement value (a "+repl.luaGetType()+")" );
}
vm.lua_pop( 1 );
lbuf.append( repl.luaAsString() );
}
int push_captures( boolean wholeMatch, int soff, int end ) {
int nlevels = ( this.level == 0 && wholeMatch ) ? 1 : this.level;
for ( int i = 0; i < nlevels; ++i ) {
push_onecapture( i, soff, end );
}
return nlevels;
}
private void push_onecapture( int i, int soff, int end ) {
if ( i >= this.level ) {
if ( i == 0 ) {
vm.push( s.substring( soff, end ) );
} else {
vm.lua_error( "invalid capture index" );
}
} else {
int l = clen[i];
@@ -454,27 +583,25 @@ public class StrLib {
}
}
static boolean isalpha( int c ) {
return ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' );
}
static boolean match_class( int c, int cl ) {
final char lcl = Character.toLowerCase( (char) cl );
int cdata = CHAR_TABLE[c];
boolean res;
switch ( Character.toLowerCase( (char) c ) ) {
case 'a': res = isalpha( c ); break;
case 'd': res = Character.isDigit( (char) c ); break;
case 'l': res = Character.isLowerCase( (char) c ); break;
case 'u': res = Character.isUpperCase( (char) c ); break;
switch ( lcl ) {
case 'a': res = ( cdata & MASK_ALPHA ) != 0; break;
case 'd': res = ( cdata & MASK_DIGIT ) != 0; break;
case 'l': res = ( cdata & MASK_LOWERCASE ) != 0; break;
case 'u': res = ( cdata & MASK_UPPERCASE ) != 0; break;
case 'c': res = ( cdata & MASK_CONTROL ) != 0; break;
case 'p': res = ( cdata & MASK_PUNCT ) != 0; break;
case 's': res = ( cdata & MASK_SPACE ) != 0; break;
case 'w': res = ( cdata & ( MASK_ALPHA | MASK_DIGIT ) ) != 0; break;
case 'x': res = ( cdata & MASK_HEXDIGIT ) != 0; break;
case 'z': res = ( c == 0 ); break;
case 'c':
case 'p':
case 's':
case 'w':
case 'x':
throw new RuntimeException("match: unimplemented: %" + (char)cl );
default: return cl == c;
}
return ( Character.isLowerCase( (char) cl ) ? res : !res );
return ( lcl == cl ) ? res : !res;
}
boolean matchbracketclass( int c, int poff, int ec ) {
@@ -521,10 +648,10 @@ public class StrLib {
return soffset;
switch ( p.luaByte( poffset ) ) {
case '(':
if ( p.luaByte( poffset + 1 ) == ')' )
return start_capture( soffset, poffset + 2, CAP_POSITION );
if ( ++poffset < p.length() && p.luaByte( poffset ) == ')' )
return start_capture( soffset, poffset + 1, CAP_POSITION );
else
return start_capture( soffset, poffset + 1, CAP_UNFINISHED );
return start_capture( soffset, poffset, CAP_UNFINISHED );
case ')':
return end_capture( soffset, poffset + 1 );
case L_ESC:

View File

@@ -11,10 +11,6 @@ public interface VM {
// ================ interfaces for performing calls
/** Prepare the VM stack for a new call with arguments to be pushed
*/
public void newCall();
/** Push an argument or return value onto the stack
*/
public void push( LValue value );
@@ -163,6 +159,13 @@ public interface VM {
*/
public void lua_error(String message);
/**
* Run the method on the stack, propagating any error that occurs.
* @param nArgs number of arguments on the stack
* @param nResults number of results on the stack
*/
public void lua_call(int nArgs, int nResults);
/**
* Run the method on the stack in protected mode.
* @param nArgs number of arguments on the stack
@@ -186,4 +189,9 @@ public interface VM {
* @return
*/
public LValue lua_tolvalue(int i);
/**
* Pop some number of items off the stack.
*/
public void lua_pop(int n);
}

View File

@@ -12,27 +12,18 @@ public class LFunction extends LValue {
}
public void luaSetTable(VM vm, LValue table, LValue key, LValue val) {
vm.newCall();
vm.push( this );
vm.push( table );
vm.push( key );
vm.push( val );
vm.setExpectedResultCount( 0 );
if ( this.luaStackCall( vm ) )
vm.execute();
else
vm.adjustResults();
vm.lua_call( 3, 0 );
}
public void luaGetTable(VM vm, LValue table, LValue key) {
vm.newCall();
vm.push( this );
vm.push( table );
vm.push( key );
vm.setExpectedResultCount( 1 );
if ( this.luaStackCall( vm ) )
vm.execute();
vm.adjustResults();
vm.lua_call( 2, 1 );
}
public LString luaGetType() {

View File

@@ -15,6 +15,7 @@ import junit.framework.TestSuite;
import lua.Builtin;
import lua.StackState;
import lua.addon.luacompat.LuaCompat;
import lua.debug.DebugStackState;
import lua.io.Closure;
import lua.io.LoadState;
import lua.io.Proto;
@@ -79,7 +80,7 @@ public class StandardTest extends TestCase {
// the garbage collector. Until we implement that, remove the
// built-in collectgarbage function.
GlobalState.getGlobalsTable().put( "collectgarbage", LNil.NIL );
StackState state = new StackState();
StackState state = new DebugStackState();
Closure c = new Closure( state, code );
ByteArrayOutputStream output = new ByteArrayOutputStream();
@@ -88,13 +89,15 @@ public class StandardTest extends TestCase {
try {
state.doCall( c, new LValue[0] );
} catch ( RuntimeException exn ) {
StackTraceElement[] stackTrace = new StackTraceElement[state.cc+1];
for ( int i = 0; i <= state.cc; ++i ) {
final int ncalls = Math.min( state.calls.length, state.cc+1 );
StackTraceElement[] stackTrace = new StackTraceElement[ncalls];
for ( int i = 0; i < ncalls; ++i ) {
CallInfo call = state.calls[i];
Proto p = call.closure.p;
int line = p.lineinfo[call.pc-1];
String func = call.closure.luaAsString().toJavaString();
stackTrace[state.cc - i] = new StackTraceElement(getName(), func, getName()+".lua", line );
stackTrace[ncalls - i - 1] = new StackTraceElement(getName(), func, getName()+".lua", line );
}
RuntimeException newExn = new RuntimeException( exn );

View File

@@ -1,3 +1,7 @@
print( string.find( 'alo alx 123 b\0o b\0o', '(..*) %1' ) )
print( string.find( 'aloALO', '%l*' ) )
print( string.find( ' \n isto <20> assim', '%S%S*' ) )
print( string.find( "", "" ) )
print( string.find( "ababaabbaba", "abb" ) )
print( string.find( "ababaabbaba", "abb", 7 ) )
@@ -19,3 +23,23 @@ print( string.byte("hi", -3) )
print( tostring(1234567890123) )
print( tostring(1234567890124) )
print( tostring(1234567890125) )
function f1(s, p)
print(p)
p = string.gsub(p, "%%([0-9])", function (s) return "%" .. (s+1) end)
print(p)
p = string.gsub(p, "^(^?)", "%1()", 1)
print(p)
p = string.gsub(p, "($?)$", "()%1", 1)
print(p)
local t = {string.match(s, p)}
return string.sub(s, t[1], t[#t] - 1)
end
print( f1('alo alx 123 b\0o b\0o', '(..*) %1') )
local function badpat()
print( string.gsub( "alo", "(.)", "%2" ) )
end
print( pcall( badpat ) )

Binary file not shown.