/******************************************************************************* * Copyright (c) 2009-2011 Luaj.org. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. ******************************************************************************/ package org.luaj.vm2; import java.io.ByteArrayInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import org.luaj.vm2.lib.MathLib; import org.luaj.vm2.lib.StringLib; /** * Subclass of {@link LuaValue} for representing lua strings. * <p> * Because lua string values are more nearly sequences of bytes than * sequences of characters or unicode code points, the {@link LuaString} * implementation holds the string value in an internal byte array. * <p> * {@link LuaString} values are generally not mutable once constructed, * so multiple {@link LuaString} values can chare a single byte array. * <p> * Currently {@link LuaString}s are pooled via a centrally managed weak table. * To ensure that as many string values as possible take advantage of this, * Constructors are not exposed directly. As with number, booleans, and nil, * instance construction should be via {@link LuaValue#valueOf(byte[])} or similar API. * <p> * When Java Strings are used to initialize {@link LuaString} data, the UTF8 encoding is assumed. * The functions * {@link LuaString#lengthAsUtf8(char[]), * {@link LuaString#encodeToUtf8(char[], int, byte[], int)}, and * {@link LuaString#decodeAsUtf8(byte[], int, int) * are used to convert back and forth between UTF8 byte arrays and character arrays. * * @see LuaValue * @see LuaValue#valueOf(String) * @see LuaValue#valueOf(byte[]) */ public class LuaString extends LuaValue { /** Size of cache of recent short strings. This is the maximum number of LuaStrings that * will be retained in the cache of recent short strings. */ public static final int RECENT_STRINGS_CACHE_SIZE = 128; /** Maximum length of a string to be considered for recent short strings caching. * This effectively limits the total memory that can be spent on the recent strings cache, * ecause no LuaString whose backing exceeds this length will be put into the cache. */ public static final int RECENT_STRINGS_MAX_LENGTH = 32; /** The singleton instance representing lua {@code true} */ public static LuaValue s_metatable; /** The bytes for the string */ public final byte[] m_bytes; /** The offset into the byte array, 0 means start at the first byte */ public final int m_offset; /** The number of bytes that comprise this string */ public final int m_length; private static class Cache { /** Simple cache of recently created strings that are short. * This is simply a list of strings, indexed by their hash codes modulo the cache size * that have been recently constructed. If a string is being constructed frequently * from different contexts, it will generally may show up as a cache hit and resolve * to the same value. */ public final LuaString recent_short_strings[] = new LuaString[RECENT_STRINGS_CACHE_SIZE]; public LuaString get(LuaString s) { final int index = s.hashCode() & (RECENT_STRINGS_CACHE_SIZE - 1); final LuaString cached = (LuaString) recent_short_strings[index]; if (cached != null && s.raweq(cached)) return cached; recent_short_strings[index] = s; return s; } static final Cache instance = new Cache(); } /** * Get a {@link LuaString} instance whose bytes match * the supplied Java String using the UTF8 encoding. * @param string Java String containing characters to encode as UTF8 * @return {@link LuaString} with UTF8 bytes corresponding to the supplied String */ public static LuaString valueOf(String string) { char[] c = string.toCharArray(); byte[] b = new byte[lengthAsUtf8(c)]; encodeToUtf8(c, c.length, b, 0); return valueOf(b, 0, b.length); } // TODO: should this be deprecated or made private? /** Construct a {@link LuaString} around a byte array that may be used directly as the backing. * <p> * The array may be used as the backing for this object, so clients must not change contents. * If the supplied value for 'len' is more than half the length of the container, the * supplied byte array will be used as the backing, otherwise the bytes will be copied to a * new byte array, and cache lookup may be performed. * <p> * @param bytes byte buffer * @param off offset into the byte buffer * @param len length of the byte buffer * @return {@link LuaString} wrapping the byte buffer */ public static LuaString valueOf(byte[] bytes, int off, int len) { if (bytes.length < RECENT_STRINGS_MAX_LENGTH) { // Short string. Reuse the backing and check the cache of recent strings before returning. final LuaString s = new LuaString(bytes, off, len); return Cache.instance.get( s ); } else if (len >= bytes.length / 2) { // Reuse backing only when more than half the bytes are part of the result. return new LuaString(bytes, off, len); } else { // Short result relative to the source. Copy only the bytes that are actually to be used. final byte[] b = new byte[len]; System.arraycopy(bytes, off, b, 0, len); return valueOf(b, 0, len); // To possibly use cached version. } } /** Construct a {@link LuaString} using the supplied characters as byte values. * <p> * Only the low-order 8-bits of each character are used, the remainder is ignored. * <p> * This is most useful for constructing byte sequences that do not conform to UTF8. * @param bytes array of char, whose values are truncated at 8-bits each and put into a byte array. * @return {@link LuaString} wrapping a copy of the byte buffer */ public static LuaString valueOf(char[] bytes) { return valueOf(bytes, 0, bytes.length); } /** Construct a {@link LuaString} using the supplied characters as byte values. * <p> * Only the low-order 8-bits of each character are used, the remainder is ignored. * <p> * This is most useful for constructing byte sequences that do not conform to UTF8. * @param bytes array of char, whose values are truncated at 8-bits each and put into a byte array. * @return {@link LuaString} wrapping a copy of the byte buffer */ public static LuaString valueOf(char[] bytes, int off, int len) { byte[] b = new byte[len]; for ( int i=0; i<len; i++ ) b[i] = (byte) bytes[i + off]; return valueOf(b, 0, len); } /** Construct a {@link LuaString} around a byte array without copying the contents. * <p> * The array may be used directly as the backing, so clients must not change contents. * <p> * @param bytes byte buffer * @return {@link LuaString} wrapping the byte buffer */ public static LuaString valueOf(byte[] bytes) { return valueOf(bytes, 0, bytes.length); } /** Construct a {@link LuaString} around a byte array without copying the contents. * <p> * The array is used directly after this is called, so clients must not change contents. * <p> * @param bytes byte buffer * @param offset offset into the byte buffer * @param length length of the byte buffer * @return {@link LuaString} wrapping the byte buffer */ private LuaString(byte[] bytes, int offset, int length) { this.m_bytes = bytes; this.m_offset = offset; this.m_length = length; } public boolean isstring() { return true; } public LuaValue getmetatable() { return s_metatable; } public int type() { return LuaValue.TSTRING; } public String typename() { return "string"; } public String tojstring() { return decodeAsUtf8(m_bytes, m_offset, m_length); } // get is delegated to the string library public LuaValue get(LuaValue key) { return s_metatable!=null? gettable(this,key): StringLib.instance.get(key); } // unary operators public LuaValue neg() { double d = scannumber(); return Double.isNaN(d)? super.neg(): valueOf(-d); } // basic binary arithmetic public LuaValue add( LuaValue rhs ) { double d = scannumber(); return Double.isNaN(d)? arithmt(ADD,rhs): rhs.add(d); } public LuaValue add( double rhs ) { return valueOf( checkarith() + rhs ); } public LuaValue add( int rhs ) { return valueOf( checkarith() + rhs ); } public LuaValue sub( LuaValue rhs ) { double d = scannumber(); return Double.isNaN(d)? arithmt(SUB,rhs): rhs.subFrom(d); } public LuaValue sub( double rhs ) { return valueOf( checkarith() - rhs ); } public LuaValue sub( int rhs ) { return valueOf( checkarith() - rhs ); } public LuaValue subFrom( double lhs ) { return valueOf( lhs - checkarith() ); } public LuaValue mul( LuaValue rhs ) { double d = scannumber(); return Double.isNaN(d)? arithmt(MUL,rhs): rhs.mul(d); } public LuaValue mul( double rhs ) { return valueOf( checkarith() * rhs ); } public LuaValue mul( int rhs ) { return valueOf( checkarith() * rhs ); } public LuaValue pow( LuaValue rhs ) { double d = scannumber(); return Double.isNaN(d)? arithmt(POW,rhs): rhs.powWith(d); } public LuaValue pow( double rhs ) { return MathLib.dpow(checkarith(),rhs); } public LuaValue pow( int rhs ) { return MathLib.dpow(checkarith(),rhs); } public LuaValue powWith( double lhs ) { return MathLib.dpow(lhs, checkarith()); } public LuaValue powWith( int lhs ) { return MathLib.dpow(lhs, checkarith()); } public LuaValue div( LuaValue rhs ) { double d = scannumber(); return Double.isNaN(d)? arithmt(DIV,rhs): rhs.divInto(d); } public LuaValue div( double rhs ) { return LuaDouble.ddiv(checkarith(),rhs); } public LuaValue div( int rhs ) { return LuaDouble.ddiv(checkarith(),rhs); } public LuaValue divInto( double lhs ) { return LuaDouble.ddiv(lhs, checkarith()); } public LuaValue mod( LuaValue rhs ) { double d = scannumber(); return Double.isNaN(d)? arithmt(MOD,rhs): rhs.modFrom(d); } public LuaValue mod( double rhs ) { return LuaDouble.dmod(checkarith(), rhs); } public LuaValue mod( int rhs ) { return LuaDouble.dmod(checkarith(), rhs); } public LuaValue modFrom( double lhs ) { return LuaDouble.dmod(lhs, checkarith()); } // relational operators, these only work with other strings public LuaValue lt( LuaValue rhs ) { return rhs.strcmp(this)>0? LuaValue.TRUE: FALSE; } public boolean lt_b( LuaValue rhs ) { return rhs.strcmp(this)>0; } public boolean lt_b( int rhs ) { typerror("attempt to compare string with number"); return false; } public boolean lt_b( double rhs ) { typerror("attempt to compare string with number"); return false; } public LuaValue lteq( LuaValue rhs ) { return rhs.strcmp(this)>=0? LuaValue.TRUE: FALSE; } public boolean lteq_b( LuaValue rhs ) { return rhs.strcmp(this)>=0; } public boolean lteq_b( int rhs ) { typerror("attempt to compare string with number"); return false; } public boolean lteq_b( double rhs ) { typerror("attempt to compare string with number"); return false; } public LuaValue gt( LuaValue rhs ) { return rhs.strcmp(this)<0? LuaValue.TRUE: FALSE; } public boolean gt_b( LuaValue rhs ) { return rhs.strcmp(this)<0; } public boolean gt_b( int rhs ) { typerror("attempt to compare string with number"); return false; } public boolean gt_b( double rhs ) { typerror("attempt to compare string with number"); return false; } public LuaValue gteq( LuaValue rhs ) { return rhs.strcmp(this)<=0? LuaValue.TRUE: FALSE; } public boolean gteq_b( LuaValue rhs ) { return rhs.strcmp(this)<=0; } public boolean gteq_b( int rhs ) { typerror("attempt to compare string with number"); return false; } public boolean gteq_b( double rhs ) { typerror("attempt to compare string with number"); return false; } // concatenation public LuaValue concat(LuaValue rhs) { return rhs.concatTo(this); } public Buffer concat(Buffer rhs) { return rhs.concatTo(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 valueOf(b, 0, b.length); } // string comparison public int strcmp(LuaValue lhs) { return -lhs.strcmp(this); } public int strcmp(LuaString rhs) { for ( int i=0, j=0; i<m_length && j<rhs.m_length; ++i, ++j ) { if ( m_bytes[m_offset+i] != rhs.m_bytes[rhs.m_offset+j] ) { return ((int)m_bytes[m_offset+i]) - ((int) rhs.m_bytes[rhs.m_offset+j]); } } return m_length - rhs.m_length; } /** Check for number in arithmetic, or throw aritherror */ private double checkarith() { double d = scannumber(); if ( Double.isNaN(d) ) aritherror(); return d; } public int checkint() { return (int) (long) checkdouble(); } public LuaInteger checkinteger() { return valueOf(checkint()); } public long checklong() { return (long) checkdouble(); } public double checkdouble() { double d = scannumber(); if ( Double.isNaN(d) ) argerror("number"); return d; } public LuaNumber checknumber() { return valueOf(checkdouble()); } public LuaNumber checknumber(String msg) { double d = scannumber(); if ( Double.isNaN(d) ) error(msg); return valueOf(d); } public boolean isnumber() { double d = scannumber(); return ! Double.isNaN(d); } public boolean isint() { double d = scannumber(); if ( Double.isNaN(d) ) return false; int i = (int) d; return i == d; } public boolean islong() { double d = scannumber(); if ( Double.isNaN(d) ) return false; long l = (long) d; return l == d; } public byte tobyte() { return (byte) toint(); } public char tochar() { return (char) toint(); } public double todouble() { double d=scannumber(); return Double.isNaN(d)? 0: d; } public float tofloat() { return (float) todouble(); } public int toint() { return (int) tolong(); } public long tolong() { return (long) todouble(); } public short toshort() { return (short) toint(); } public double optdouble(double defval) { return checknumber().checkdouble(); } public int optint(int defval) { return checknumber().checkint(); } public LuaInteger optinteger(LuaInteger defval) { return checknumber().checkinteger(); } public long optlong(long defval) { return checknumber().checklong(); } public LuaNumber optnumber(LuaNumber defval) { return checknumber().checknumber(); } public LuaString optstring(LuaString defval) { return this; } public LuaValue tostring() { return this; } public String optjstring(String defval) { return tojstring(); } public LuaString strvalue() { return this; } /** Take a substring using Java zero-based indexes for begin and end or range. * @param beginIndex The zero-based index of the first character to include. * @param endIndex The zero-based index of position after the last character. * @return LuaString which is a substring whose first character is at offset * beginIndex and extending for (endIndex - beginIndex ) characters. */ public LuaString substring( int beginIndex, int endIndex ) { return valueOf( m_bytes, m_offset + beginIndex, endIndex - beginIndex ); } public int hashCode() { int h = m_length; /* seed */ int step = (m_length>>5)+1; /* if string is too long, don't hash all its chars */ for (int l1=m_length; l1>=step; l1-=step) /* compute hash */ h = h ^ ((h<<5)+(h>>2)+(((int) m_bytes[m_offset+l1-1] ) & 0x0FF )); return h; } // object comparison, used in key comparison public boolean equals( Object o ) { if ( o instanceof LuaString ) { return raweq( (LuaString) o ); } return false; } // equality w/ metatable processing public LuaValue eq( LuaValue val ) { return val.raweq(this)? TRUE: FALSE; } public boolean eq_b( LuaValue val ) { return val.raweq(this); } // equality w/o metatable processing public boolean raweq( LuaValue val ) { return val.raweq(this); } public boolean raweq( LuaString s ) { if ( this == s ) return true; if ( s.m_length != m_length ) return false; if ( s.m_bytes == m_bytes && s.m_offset == m_offset ) return true; if ( s.hashCode() != hashCode() ) return false; for ( int i=0; i<m_length; i++ ) if ( s.m_bytes[s.m_offset+i] != m_bytes[m_offset+i] ) return false; return true; } public static boolean equals( LuaString a, int i, LuaString b, int j, int n ) { return equals( a.m_bytes, a.m_offset + i, b.m_bytes, b.m_offset + j, n ); } public static boolean equals( byte[] a, int i, byte[] b, int j, int n ) { if ( a.length < i + n || b.length < j + n ) return false; while ( --n>=0 ) if ( a[i++]!=b[j++] ) return false; return true; } public void write(DataOutputStream writer, int i, int len) throws IOException { writer.write(m_bytes,m_offset+i,len); } public LuaValue len() { return LuaInteger.valueOf(m_length); } public int length() { return m_length; } public int rawlen() { return m_length; } public int luaByte(int index) { return m_bytes[m_offset + index] & 0x0FF; } public int charAt( int index ) { if ( index < 0 || index >= m_length ) throw new IndexOutOfBoundsException(); return luaByte( index ); } public String checkjstring() { return tojstring(); } public LuaString checkstring() { return this; } /** Convert value to an input stream. * * @return {@link InputStream} whose data matches the bytes in this {@link LuaString} */ public InputStream toInputStream() { return new ByteArrayInputStream(m_bytes, m_offset, m_length); } /** * Copy the bytes of the string into the given byte array. * @param strOffset offset from which to copy * @param bytes destination byte array * @param arrayOffset offset in destination * @param len number of bytes to copy */ public void copyInto( int strOffset, byte[] bytes, int arrayOffset, int len ) { System.arraycopy( m_bytes, m_offset+strOffset, bytes, arrayOffset, len ); } /** Java version of strpbrk - find index of any byte that in an accept string. * @param accept {@link LuaString} containing characters to look for. * @return index of first match in the {@code accept} string, or -1 if not found. */ public int indexOfAny( LuaString accept ) { final int ilimit = m_offset + m_length; final int jlimit = accept.m_offset + accept.m_length; for ( int i = m_offset; i < ilimit; ++i ) { for ( int j = accept.m_offset; j < jlimit; ++j ) { if ( m_bytes[i] == accept.m_bytes[j] ) { return i - m_offset; } } } return -1; } /** * Find the index of a byte starting at a point in this string * @param b the byte to look for * @param start the first index in the string * @return index of first match found, or -1 if not found. */ public int indexOf( byte b, int start ) { for ( int i=start; i < m_length; ++i ) { if ( m_bytes[m_offset+i] == b ) return i; } return -1; } /** * Find the index of a string starting at a point in this string * @param s the string to search for * @param start the first index in the string * @return index of first match found, or -1 if not found. */ public int indexOf( LuaString s, int start ) { final int slen = s.length(); final int limit = m_length - slen; for ( int i=start; i <= limit; ++i ) { if ( equals( m_bytes, m_offset+i, s.m_bytes, s.m_offset, slen ) ) return i; } return -1; } /** * Find the last index of a string in this string * @param s the string to search for * @return index of last match found, or -1 if not found. */ public int lastIndexOf( LuaString s ) { final int slen = s.length(); final int limit = m_length - slen; for ( int i=limit; i >= 0; --i ) { if ( equals( m_bytes, m_offset+i, s.m_bytes, s.m_offset, slen ) ) return i; } return -1; } /** * Convert to Java String interpreting as utf8 characters. * * @param bytes byte array in UTF8 encoding to convert * @param offset starting index in byte array * @param length number of bytes to convert * @return Java String corresponding to the value of bytes interpreted using UTF8 * @see #lengthAsUtf8(char[]) * @see #encodeToUtf8(char[], int, byte[], int) * @see #isValidUtf8() */ public static String decodeAsUtf8(byte[] bytes, int offset, int length) { int i,j,n,b; for ( i=offset,j=offset+length,n=0; i<j; ++n ) { switch ( 0xE0 & bytes[i++] ) { case 0xE0: ++i; case 0xC0: ++i; } } char[] chars=new char[n]; for ( i=offset,j=offset+length,n=0; i<j; ) { chars[n++] = (char) ( ((b=bytes[i++])>=0||i>=j)? b: (b<-32||i+1>=j)? (((b&0x3f) << 6) | (bytes[i++]&0x3f)): (((b&0xf) << 12) | ((bytes[i++]&0x3f)<<6) | (bytes[i++]&0x3f))); } return new String(chars); } /** * Count the number of bytes required to encode the string as UTF-8. * @param chars Array of unicode characters to be encoded as UTF-8 * @return count of bytes needed to encode using UTF-8 * @see #encodeToUtf8(char[], int, byte[], int) * @see #decodeAsUtf8(byte[], int, int) * @see #isValidUtf8() */ public static int lengthAsUtf8(char[] chars) { int i,b; char c; for ( i=b=chars.length; --i>=0; ) if ( (c=chars[i]) >=0x80 ) b += (c>=0x800)? 2: 1; return b; } /** * Encode the given Java string as UTF-8 bytes, writing the result to bytes * starting at offset. * <p> * The string should be measured first with lengthAsUtf8 * to make sure the given byte array is large enough. * @param chars Array of unicode characters to be encoded as UTF-8 * @param nchars Number of characters in the array to convert. * @param bytes byte array to hold the result * @param off offset into the byte array to start writing * @return number of bytes converted. * @see #lengthAsUtf8(char[]) * @see #decodeAsUtf8(byte[], int, int) * @see #isValidUtf8() */ public static int encodeToUtf8(char[] chars, int nchars, byte[] bytes, int off) { char c; int j = off; for ( int i=0; i<nchars; i++ ) { if ( (c = chars[i]) < 0x80 ) { bytes[j++] = (byte) c; } else if ( c < 0x800 ) { bytes[j++] = (byte) (0xC0 | ((c>>6) & 0x1f)); bytes[j++] = (byte) (0x80 | ( c & 0x3f)); } else { bytes[j++] = (byte) (0xE0 | ((c>>12) & 0x0f)); bytes[j++] = (byte) (0x80 | ((c>>6) & 0x3f)); bytes[j++] = (byte) (0x80 | ( c & 0x3f)); } } return j - off; } /** Check that a byte sequence is valid UTF-8 * @return true if it is valid UTF-8, otherwise false * @see #lengthAsUtf8(char[]) * @see #encodeToUtf8(char[], int, byte[], int) * @see #decodeAsUtf8(byte[], int, int) */ public boolean isValidUtf8() { int i,j,n,b,e=0; for ( i=m_offset,j=m_offset+m_length,n=0; i<j; ++n ) { int c = m_bytes[i++]; if ( c >= 0 ) continue; if ( ((c & 0xE0) == 0xC0) && i<j && (m_bytes[i++] & 0xC0) == 0x80) continue; if ( ((c & 0xF0) == 0xE0) && i+1<j && (m_bytes[i++] & 0xC0) == 0x80 && (m_bytes[i++] & 0xC0) == 0x80) continue; return false; } return true; } // --------------------- number conversion ----------------------- /** * convert to a number using baee 10 or base 16 if it starts with '0x', * or NIL if it can't be converted * @return IntValue, DoubleValue, or NIL depending on the content of the string. * @see LuaValue#tonumber() */ public LuaValue tonumber() { double d = scannumber(); return Double.isNaN(d)? NIL: valueOf(d); } /** * convert to a number using a supplied base, or NIL if it can't be converted * @param base the base to use, such as 10 * @return IntValue, DoubleValue, or NIL depending on the content of the string. * @see LuaValue#tonumber() */ public LuaValue tonumber( int base ) { double d = scannumber( base ); return Double.isNaN(d)? NIL: valueOf(d); } /** * Convert to a number in base 10, or base 16 if the string starts with '0x', * or return Double.NaN if it cannot be converted to a number. * @return double value if conversion is valid, or Double.NaN if not */ public double scannumber() { int i=m_offset,j=m_offset+m_length; while ( i<j && m_bytes[i]==' ' ) ++i; while ( i<j && m_bytes[j-1]==' ' ) --j; if ( i>=j ) return Double.NaN; if ( m_bytes[i]=='0' && i+1<j && (m_bytes[i+1]=='x'||m_bytes[i+1]=='X')) return scanlong(16, i+2, j); double l = scanlong(10, i, j); return Double.isNaN(l)? scandouble(i,j): l; } /** * Convert to a number in a base, or return Double.NaN if not a number. * @param base the base to use between 2 and 36 * @return double value if conversion is valid, or Double.NaN if not */ public double scannumber(int base) { if ( base < 2 || base > 36 ) return Double.NaN; int i=m_offset,j=m_offset+m_length; while ( i<j && m_bytes[i]==' ' ) ++i; while ( i<j && m_bytes[j-1]==' ' ) --j; if ( i>=j ) return Double.NaN; return scanlong( base, i, j ); } /** * Scan and convert a long value, or return Double.NaN if not found. * @param base the base to use, such as 10 * @param start the index to start searching from * @param end the first index beyond the search range * @return double value if conversion is valid, * or Double.NaN if not */ private double scanlong( int base, int start, int end ) { long x = 0; boolean neg = (m_bytes[start] == '-'); for ( int i=(neg?start+1:start); i<end; i++ ) { int digit = m_bytes[i] - (base<=10||(m_bytes[i]>='0'&&m_bytes[i]<='9')? '0': m_bytes[i]>='A'&&m_bytes[i]<='Z'? ('A'-10): ('a'-10)); if ( digit < 0 || digit >= base ) return Double.NaN; x = x * base + digit; if ( x < 0 ) return Double.NaN; // overflow } return neg? -x: x; } /** * Scan and convert a double value, or return Double.NaN if not a double. * @param start the index to start searching from * @param end the first index beyond the search range * @return double value if conversion is valid, * or Double.NaN if not */ private double scandouble(int start, int end) { if ( end>start+64 ) end=start+64; for ( int i=start; i<end; i++ ) { switch ( m_bytes[i] ) { case '-': case '+': case '.': case 'e': case 'E': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': break; default: return Double.NaN; } } char [] c = new char[end-start]; for ( int i=start; i<end; i++ ) c[i-start] = (char) m_bytes[i]; try { return Double.parseDouble(new String(c)); } catch ( Exception e ) { return Double.NaN; } } /** * Print the bytes of the LuaString to a PrintStream as if it were * an ASCII string, quoting and escaping control characters. * @param ps PrintStream to print to. */ public void printToStream(PrintStream ps) { for (int i = 0, n = m_length; i < n; i++) { int c = m_bytes[m_offset+i]; ps.print((char) c); } } }