From 02b22b1e0deb6ea4c2bc925762f7a95ab50e2343 Mon Sep 17 00:00:00 2001 From: James Roseborough Date: Sun, 22 Aug 2010 17:35:08 +0000 Subject: [PATCH] Refactor concat, add __concat metatag --- src/core/org/luaj/vm2/Buffer.java | 108 ++++++++++++------ src/core/org/luaj/vm2/LuaClosure.java | 12 +- src/core/org/luaj/vm2/LuaDouble.java | 6 +- src/core/org/luaj/vm2/LuaInteger.java | 4 - src/core/org/luaj/vm2/LuaNumber.java | 6 + src/core/org/luaj/vm2/LuaString.java | 11 +- src/core/org/luaj/vm2/LuaValue.java | 19 ++- test/junit/org/luaj/vm2/TypeTest.java | 18 +-- .../luaj/vm2/UnaryBinaryOperatorsTest.java | 74 ++++++++++++ test/lua/metatags.lua | 33 +++++- 10 files changed, 227 insertions(+), 64 deletions(-) diff --git a/src/core/org/luaj/vm2/Buffer.java b/src/core/org/luaj/vm2/Buffer.java index dc789c9a..ca875646 100644 --- a/src/core/org/luaj/vm2/Buffer.java +++ b/src/core/org/luaj/vm2/Buffer.java @@ -28,9 +28,12 @@ package org.luaj.vm2; */ public final class Buffer { private static final int DEFAULT_CAPACITY = 64; + private static final byte[] NOBYTES = {}; private byte[] bytes; private int length; + private int offset; + private LuaValue value; public Buffer() { this(DEFAULT_CAPACITY); @@ -39,63 +42,98 @@ public final class Buffer { public Buffer( int initialCapacity ) { bytes = new byte[ initialCapacity ]; length = 0; + offset = 0; + value = null; } - public final String tojstring() { - return LuaString.valueOf(bytes, 0, length).tojstring(); + public Buffer(LuaValue value) { + bytes = NOBYTES; + length = offset = 0; + this.value = value; } - public final Buffer append( byte b ) { - ensureCapacity( length + 1 ); - bytes[ length++ ] = b; + public LuaValue value() { + return value != null? value: this.tostring(); + } + + public Buffer setvalue(LuaValue value) { + bytes = NOBYTES; + offset = length = 0; + this.value = value; return this; } + public final LuaString tostring() { + realloc( length, 0 ); + return LuaString.valueOf( bytes, offset, length ); + } + + public String tojstring() { + return value().tojstring(); + } + + public String toString() { + return tojstring(); + } + + public final Buffer append( byte b ) { + makeroom( 0, 1 ); + bytes[ offset + length++ ] = b; + return this; + } + public final Buffer append( LuaValue val ) { - if ( ! val.isstring() ) - val.error("attempt to concatenate a '"+val.typename()+"' value"); append( val.strvalue() ); return this; } public final Buffer append( LuaString str ) { - final int alen = str.length(); - ensureCapacity( length + alen ); - str.copyInto( 0, bytes, length, alen ); - length += alen; + final int n = str.m_length; + makeroom( 0, n ); + str.copyInto( 0, bytes, offset + length, n ); + length += n; return this; } public final Buffer append( String str ) { char[] chars = str.toCharArray(); - final int alen = LuaString.lengthAsUtf8( chars ); - ensureCapacity( length + alen ); - LuaString.encodeToUtf8( chars, bytes, length ); - length += alen; + final int n = LuaString.lengthAsUtf8( chars ); + makeroom( 0, n ); + LuaString.encodeToUtf8( chars, bytes, offset + length ); + length += n; + return this; + } + + public Buffer prepend(LuaString s) { + int n = s.m_length; + makeroom( n, 0 ); + System.arraycopy( s.m_bytes, s.m_offset, bytes, offset-n, n ); + offset -= n; + length += n; + value = null; return this; } - public final void setLength( int length ) { - ensureCapacity( length ); - this.length = length; + public final void makeroom( int nbefore, int nafter ) { + if ( value != null ) { + LuaString s = value.strvalue(); + value = null; + bytes = new byte[nbefore+s.m_length+nafter]; + length = s.m_length; + offset = nbefore; + System.arraycopy(s.m_bytes, s.m_offset, bytes, offset, length); + } else if ( offset+length+nafter > bytes.length || offset bytes.length ) - realloc( minSize ); - } - - private final void realloc( int minSize ) { - bytes = realloc( bytes, Math.max( bytes.length * 2, minSize ) ); - } - - private final 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; + private final void realloc( int newSize, int newOffset ) { + if ( newSize != bytes.length ) { + byte[] newBytes = new byte[ newSize ]; + System.arraycopy( bytes, offset, newBytes, newOffset, length ); + bytes = newBytes; + offset = newOffset; + } } + } diff --git a/src/core/org/luaj/vm2/LuaClosure.java b/src/core/org/luaj/vm2/LuaClosure.java index a25f6fa2..4b7088bb 100644 --- a/src/core/org/luaj/vm2/LuaClosure.java +++ b/src/core/org/luaj/vm2/LuaClosure.java @@ -236,10 +236,14 @@ public class LuaClosure extends LuaFunction { b = i>>>23; c = (i>>14)&0x1ff; { - Buffer sb = new Buffer(); - for ( ; b<=c; ) - sb.append( stack[b++] ); - stack[a] = sb.tostring(); + LuaValue r = stack[c-1].concat(stack[c]); + if ( (c-=2) >= b ) { + Buffer sb = r.buffer(); + while ( c>=b ) + sb = stack[c--].concat(sb); + r = sb.value(); + } + stack[a] = r; } continue; diff --git a/src/core/org/luaj/vm2/LuaDouble.java b/src/core/org/luaj/vm2/LuaDouble.java index 5fd1a903..bad3640c 100644 --- a/src/core/org/luaj/vm2/LuaDouble.java +++ b/src/core/org/luaj/vm2/LuaDouble.java @@ -148,11 +148,7 @@ public class LuaDouble extends LuaNumber { // string comparison public int strcmp( LuaString rhs ) { typerror("attempt to compare number with string"); return 0; } - - // concatenation - public String concat_s(LuaValue rhs) { return rhs.concatTo_s(Double.toString(v)); } - public String concatTo_s(String lhs) { return lhs + v; } - + public String tojstring() { /* if ( v == 0.0 ) { // never occurs in J2me diff --git a/src/core/org/luaj/vm2/LuaInteger.java b/src/core/org/luaj/vm2/LuaInteger.java index 9663ba85..578646c7 100644 --- a/src/core/org/luaj/vm2/LuaInteger.java +++ b/src/core/org/luaj/vm2/LuaInteger.java @@ -165,10 +165,6 @@ public class LuaInteger extends LuaNumber { // string comparison public int strcmp( LuaString rhs ) { typerror("attempt to compare number with string"); return 0; } - // concatenation - public String concat_s(LuaValue rhs) { return rhs.concatTo_s(Integer.toString(v)); } - public String concatTo_s(String lhs) { return lhs + v; } - public int checkint() { return v; } diff --git a/src/core/org/luaj/vm2/LuaNumber.java b/src/core/org/luaj/vm2/LuaNumber.java index c5c47284..aa3d1632 100644 --- a/src/core/org/luaj/vm2/LuaNumber.java +++ b/src/core/org/luaj/vm2/LuaNumber.java @@ -61,4 +61,10 @@ public class LuaNumber extends LuaValue { public LuaValue getmetatable() { return s_metatable; } + + public LuaValue concat(LuaValue rhs) { return rhs.concatTo(this); } + public Buffer concat(Buffer rhs) { return rhs.prepend(this.strvalue()); } + public LuaValue concatTo(LuaNumber lhs) { return strvalue().concatTo(lhs.strvalue()); } + public LuaValue concatTo(LuaString lhs) { return strvalue().concatTo(lhs); } + } diff --git a/src/core/org/luaj/vm2/LuaString.java b/src/core/org/luaj/vm2/LuaString.java index aa11270d..0d8a596b 100644 --- a/src/core/org/luaj/vm2/LuaString.java +++ b/src/core/org/luaj/vm2/LuaString.java @@ -155,8 +155,15 @@ public class LuaString extends LuaValue { public boolean gteq_b( double rhs ) { typerror("attempt to compare string with number"); return false; } // concatenation - public String concat_s(LuaValue rhs) { return rhs.concatTo_s(tojstring()); } - public String concatTo_s(String lhs) { return lhs + tojstring(); } + public LuaValue concat(LuaValue rhs) { return rhs.concatTo(this); } + public Buffer concat(Buffer rhs) { return rhs.prepend(this); } + public LuaValue concatTo(LuaNumber lhs) { return concatTo(lhs.strvalue()); } + public LuaValue concatTo(LuaString lhs) { + byte[] b = new byte[lhs.m_length+this.m_length]; + System.arraycopy(lhs.m_bytes, lhs.m_offset, b, 0, lhs.m_length); + System.arraycopy(this.m_bytes, this.m_offset, b, lhs.m_length, this.m_length); + return new LuaString(b, 0, b.length); + } // string comparison public int strcmp(LuaValue lhs) { return -lhs.strcmp(this); } diff --git a/src/core/org/luaj/vm2/LuaValue.java b/src/core/org/luaj/vm2/LuaValue.java index 5c16555f..3cdcaead 100644 --- a/src/core/org/luaj/vm2/LuaValue.java +++ b/src/core/org/luaj/vm2/LuaValue.java @@ -78,6 +78,7 @@ public class LuaValue extends Varargs { public static final LuaString LT = valueOf("__lt"); public static final LuaString LE = valueOf("__le"); public static final LuaString TOSTRING = valueOf("__tostring"); + public static final LuaString CONCAT = valueOf("__concat"); public static final LuaString EMPTYSTRING = valueOf(""); private static int MAXSTACK = 250; @@ -343,9 +344,20 @@ public class LuaValue extends Varargs { public int strcmp( LuaString rhs ) { error("attempt to compare "+typename()); return 0; } // concatenation - public LuaValue concat( LuaValue rhs ) { return valueOf(concat_s(rhs)); } - public String concat_s( LuaValue rhs ) { error("attempt to concatenate "+this.typename()); return null; } - public String concatTo_s( String lhs ) { error("attempt to concatenate "+this.typename()); return null; } + public LuaValue concat(LuaValue rhs) { return this.concatmt(rhs); } + public LuaValue concatTo(LuaNumber lhs) { return lhs.concatmt(this); } + public LuaValue concatTo(LuaString lhs) { return lhs.concatmt(this); } + public Buffer buffer() { return new Buffer(this); } + public Buffer concat(Buffer rhs) { + return rhs.setvalue(checkmetatag(CONCAT,"attempt to concatenate ").call(this, rhs.value())); + } + public LuaValue concatmt(LuaValue rhs) { + LuaValue h=metatag(CONCAT); + LuaValue v=this; + if ( h.isnil() || (h=(v=rhs).metatag(CONCAT)).isnil()) + v.typerror("attempt to concatenate "); + return h.call(this,rhs); + } // boolean operators public LuaValue and( LuaValue rhs ) { return this.toboolean()? rhs: this; } @@ -577,5 +589,4 @@ public class LuaValue extends Varargs { } } - } diff --git a/test/junit/org/luaj/vm2/TypeTest.java b/test/junit/org/luaj/vm2/TypeTest.java index ec894a7b..206c568e 100644 --- a/test/junit/org/luaj/vm2/TypeTest.java +++ b/test/junit/org/luaj/vm2/TypeTest.java @@ -1116,23 +1116,23 @@ public class TypeTest extends TestCase { } public void testCheckJavaString() { - throwsErrorReq( somenil, "checkString" ); - throwsErrorReq( sometrue, "checkString" ); - throwsErrorReq( somefalse, "checkString" ); + throwsErrorReq( somenil, "checkjstring" ); + throwsErrorReq( sometrue, "checkjstring" ); + throwsErrorReq( somefalse, "checkjstring" ); assertEquals( String.valueOf(zero), zero.checkjstring() ); assertEquals( String.valueOf(intint), intint.checkjstring() ); assertEquals( String.valueOf(longdouble), longdouble.checkjstring() ); assertEquals( String.valueOf(doubledouble), doubledouble.checkjstring() ); - throwsErrorReq( somefunc, "checkString" ); - throwsErrorReq( someclosure, "checkString" ); + throwsErrorReq( somefunc, "checkjstring" ); + throwsErrorReq( someclosure, "checkjstring" ); assertEquals( samplestringstring, stringstring.checkjstring() ); assertEquals( samplestringint, stringint.checkjstring() ); assertEquals( samplestringlong, stringlong.checkjstring() ); assertEquals( samplestringdouble, stringdouble.checkjstring() ); - throwsErrorReq( thread, "checkString" ); - throwsErrorReq( table, "checkString" ); - throwsErrorReq( userdataobj, "checkString" ); - throwsErrorReq( userdatacls, "checkString" ); + throwsErrorReq( thread, "checkjstring" ); + throwsErrorReq( table, "checkjstring" ); + throwsErrorReq( userdataobj, "checkjstring" ); + throwsErrorReq( userdatacls, "checkjstring" ); } public void testCheckLuaString() { diff --git a/test/junit/org/luaj/vm2/UnaryBinaryOperatorsTest.java b/test/junit/org/luaj/vm2/UnaryBinaryOperatorsTest.java index 9cf0308d..2b4ec915 100644 --- a/test/junit/org/luaj/vm2/UnaryBinaryOperatorsTest.java +++ b/test/junit/org/luaj/vm2/UnaryBinaryOperatorsTest.java @@ -572,4 +572,78 @@ public class UnaryBinaryOperatorsTest extends TestCase { assertEquals(t, aaa.lteq(aaa)); assertEquals(t, aaa.gteq(aaa)); } + + public void testBuffer() { + LuaValue abc = LuaValue.valueOf("abcdefghi").substring(0,3); + LuaValue def = LuaValue.valueOf("abcdefghi").substring(3,6); + LuaValue ghi = LuaValue.valueOf("abcdefghi").substring(6,9); + LuaValue n123 = LuaValue.valueOf(123); + + // basic append + Buffer b = new Buffer(); assertEquals( "", b.value().tojstring() ); + b.append(def); assertEquals( "def", b.value().tojstring() ); + b.append(abc); assertEquals( "defabc", b.value().tojstring() ); + b.append(ghi); assertEquals( "defabcghi", b.value().tojstring() ); + b.append(n123); assertEquals( "defabcghi123", b.value().tojstring() ); + + // basic prepend + b = new Buffer(); assertEquals( "", b.value().tojstring() ); + b.prepend(def.strvalue()); assertEquals( "def", b.value().tojstring() ); + b.prepend(ghi.strvalue()); assertEquals( "ghidef", b.value().tojstring() ); + b.prepend(abc.strvalue()); assertEquals( "abcghidef", b.value().tojstring() ); + b.prepend(n123.strvalue()); assertEquals( "123abcghidef", b.value().tojstring() ); + + // mixed append, prepend + b = new Buffer(); assertEquals( "", b.value().tojstring() ); + b.append(def); assertEquals( "def", b.value().tojstring() ); + b.append(abc); assertEquals( "defabc", b.value().tojstring() ); + b.prepend(ghi.strvalue()); assertEquals( "ghidefabc", b.value().tojstring() ); + b.prepend(n123.strvalue()); assertEquals( "123ghidefabc", b.value().tojstring() ); + b.append(def); assertEquals( "123ghidefabcdef", b.value().tojstring() ); + b.append(abc); assertEquals( "123ghidefabcdefabc", b.value().tojstring() ); + b.prepend(ghi.strvalue()); assertEquals( "ghi123ghidefabcdefabc", b.value().tojstring() ); + b.prepend(n123.strvalue()); assertEquals( "123ghi123ghidefabcdefabc", b.value().tojstring() ); + + // value + b = new Buffer(def); assertEquals( "def", b.value().tojstring() ); + b.append(abc); assertEquals( "defabc", b.value().tojstring() ); + b.prepend(ghi.strvalue()); assertEquals( "ghidefabc", b.value().tojstring() ); + b.setvalue(def); assertEquals( "def", b.value().tojstring() ); + b.prepend(ghi.strvalue()); assertEquals( "ghidef", b.value().tojstring() ); + b.append(abc); assertEquals( "ghidefabc", b.value().tojstring() ); + } + + public void testConcat() { + LuaValue abc = LuaValue.valueOf("abcdefghi").substring(0,3); + LuaValue def = LuaValue.valueOf("abcdefghi").substring(3,6); + LuaValue ghi = LuaValue.valueOf("abcdefghi").substring(6,9); + LuaValue n123 = LuaValue.valueOf(123); + + assertEquals( "abc", abc.tojstring() ); + assertEquals( "def", def.tojstring() ); + assertEquals( "ghi", ghi.tojstring() ); + assertEquals( "123", n123.tojstring() ); + assertEquals( "abcabc", abc.concat(abc).tojstring() ); + assertEquals( "defghi", def.concat(ghi).tojstring() ); + assertEquals( "ghidef", ghi.concat(def).tojstring() ); + assertEquals( "ghidefabcghi", ghi.concat(def).concat(abc).concat(ghi).tojstring() ); + assertEquals( "123def", n123.concat(def).tojstring() ); + assertEquals( "def123", def.concat(n123).tojstring() ); + } + + public void testConcatBuffer() { + LuaValue abc = LuaValue.valueOf("abcdefghi").substring(0,3); + LuaValue def = LuaValue.valueOf("abcdefghi").substring(3,6); + LuaValue ghi = LuaValue.valueOf("abcdefghi").substring(6,9); + LuaValue n123 = LuaValue.valueOf(123); + Buffer b; + + b = new Buffer(def); assertEquals( "def", b.value().tojstring() ); + b = ghi.concat(b); assertEquals( "ghidef", b.value().tojstring() ); + b = abc.concat(b); assertEquals( "abcghidef", b.value().tojstring() ); + b = n123.concat(b); assertEquals( "123abcghidef", b.value().tojstring() ); + b.setvalue(n123); + b = def.concat(b); assertEquals( "def123", b.value().tojstring() ); + b = abc.concat(b); assertEquals( "abcdef123", b.value().tojstring() ); + } } diff --git a/test/lua/metatags.lua b/test/lua/metatags.lua index 65102437..0edfb438 100644 --- a/test/lua/metatags.lua +++ b/test/lua/metatags.lua @@ -46,6 +46,7 @@ local mt = { __metatable={}, __index=buildop('index'), __newindex=buildop('newindex'), + __concat=buildop('concat'), } -- pcall a function and check for a pattern in the error string @@ -118,8 +119,9 @@ end print( '---- __eq, __lt, __le, same types' ) local bfunction = function() end local bthread = coroutine.create( bfunction ) +local btable = {} local groups -groups = { {afunction, bfunction}, {true, true}, {true, false}, {afunction, bfunction}, {athread, bthread}, {atable, atable}, {atable, {}} } +groups = { {true, true}, {true, false}, {afunction, bfunction}, {athread, bthread}, {atable, atable}, {atable, btable} } for i=1,#groups do local a,b = groups[i][1], groups[i][2] print( type(a), type(b), 'before', pcall( function() return a==b end ) ) @@ -199,3 +201,32 @@ for i=1,#values do print( debug.setmetatable( a, nil ) ) end +print( '---- __concat' ) +groups = { {atable, afunction}, {afunction, atable}, {123, nil}, {nil, 123} } +local s,t,u = 'sss',777 +local concatresult = setmetatable( { '__concat-result' }, { + __tostring=function() + return 'concat-string-result' + end } ) +local concatmt = { + __concat=function(a,b) + print( 'mt.__concat('..type(a)..','..type(b)..')', a, b ) + return concatresult + end +} +for i=1,#groups do + local a,b = groups[i][1], groups[i][2] + print( type(a), type(b), 'before', ecall( 'attempt to concatenate ', function() return a..b end ) ) + print( type(a), type(b), 'before', ecall( 'attempt to concatenate ', function() return b..a end ) ) + print( type(a), type(s), type(t), 'before', ecall( 'attempt to concatenate ', function() return a..s..t end ) ) + print( type(s), type(a), type(t), 'before', ecall( 'attempt to concatenate ', function() return s..a..t end ) ) + print( type(s), type(t), type(a), 'before', ecall( 'attempt to concatenate ', function() return s..t..a end ) ) + print( debug.setmetatable( a, concatmt ) ) + print( type(a), type(b), 'after', pcall( function() return a..b end ) ) + print( type(a), type(b), 'after', pcall( function() return b..a end ) ) + print( type(a), type(s), type(t), 'before', pcall( function() return a..s..t end ) ) + print( type(s), type(a), type(t), 'before', ecall( 'attempt to concatenate ', function() return s..a..t end ) ) + print( type(s), type(t), type(a), 'before', ecall( 'attempt to concatenate ', function() return s..t..a end ) ) + print( debug.setmetatable( a, nil ) ) +end +