diff --git a/src/addon/java/lua/addon/luacompat/LBuffer.java b/src/addon/java/lua/addon/luacompat/LBuffer.java new file mode 100644 index 00000000..22b3d001 --- /dev/null +++ b/src/addon/java/lua/addon/luacompat/LBuffer.java @@ -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; + } +} diff --git a/src/addon/java/lua/addon/luacompat/LuaCompat.java b/src/addon/java/lua/addon/luacompat/LuaCompat.java index 7378a359..966e4588 100644 --- a/src/addon/java/lua/addon/luacompat/LuaCompat.java +++ b/src/addon/java/lua/addon/luacompat/LuaCompat.java @@ -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; diff --git a/src/addon/java/lua/addon/luacompat/StrLib.java b/src/addon/java/lua/addon/luacompat/StrLib.java index 3ae79928..fc69f2c6 100644 --- a/src/addon/java/lua/addon/luacompat/StrLib.java +++ b/src/addon/java/lua/addon/luacompat/StrLib.java @@ -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: diff --git a/src/main/java/lua/VM.java b/src/main/java/lua/VM.java index 4bd32360..4d3ae3d0 100644 --- a/src/main/java/lua/VM.java +++ b/src/main/java/lua/VM.java @@ -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); } diff --git a/src/main/java/lua/value/LFunction.java b/src/main/java/lua/value/LFunction.java index 2aadc956..288e076c 100644 --- a/src/main/java/lua/value/LFunction.java +++ b/src/main/java/lua/value/LFunction.java @@ -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() { diff --git a/src/test/java/lua/StandardTest.java b/src/test/java/lua/StandardTest.java index 1d6c9a4d..728db20b 100644 --- a/src/test/java/lua/StandardTest.java +++ b/src/test/java/lua/StandardTest.java @@ -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 ); diff --git a/src/test/res/strlib.lua b/src/test/res/strlib.lua index 54ca5876..ddccf663 100644 --- a/src/test/res/strlib.lua +++ b/src/test/res/strlib.lua @@ -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 � 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 ) ) diff --git a/src/test/res/strlib.luac b/src/test/res/strlib.luac deleted file mode 100644 index 4f219cc9..00000000 Binary files a/src/test/res/strlib.luac and /dev/null differ