/** * This file is part of Erjang - A JVM-based Erlang VM * * Copyright (c) 2009 by Trifork * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ package erjang; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import java.util.Set; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import erjang.m.ets.EMatchContext; import erjang.m.ets.EPattern; import erjang.m.ets.ETermPattern; /** * */ public class EBitString extends EObject { public static final Type EBITSTRING_TYPE = Type.getType(EBitString.class); public static final String EBITSTRING_NAME = EBITSTRING_TYPE .getInternalName(); protected final byte[] data; private final int data_offset; int byte_size; protected final int extra_bits; //private final long bits; //protected final long bitOff = 0; protected EBitString(byte[] data) { this(data.clone(), 0, data.length, 0); } @Override public int hashCode() { int h = 0; for (int i = 0; i < byte_size; i++) { h = h * 31 + data[data_offset + i]; } if (extra_bits != 0) { int val = data[data_offset + byte_size] >>> (8-extra_bits); h = h * 31 + val; } return h; } /** * @return the bits */ public long bitSize() { return byteSize() * 8L + extra_bits; } /** * @return number of bytes needed to hold this bit string (byteSize + (extrabits?1:0)) * */ protected int dataByteSize() { return byteSize() + (extra_bits > 0 ? 1 : 0); } public EBitString testBitString() { return this; } /** returns true if this bitstring matches the give <code>matcher</code>. */ public boolean match(ETermPattern matcher, EMatchContext r) { return matcher.match(this, r); } public EBinary testBinary() { if (isBinary()) { assert false : "EBitString is Binary and testBinary is not overridden."; // this should never happen return new EBinary(toByteArray()); } return null; } @Override int cmp_order() { return CMP_ORDER_BITSTRING; } @Override public Type emit_const(MethodVisitor fa) { char[] chs = new char[dataByteSize()]; for (int byteIdx = 0; byteIdx < byteSize(); byteIdx += 1) { chs[byteIdx] = (char)(data[byteIdx] & 0xff); } if (extra_bits != 0) { int rest = intBitsAt(byteSize()*8, extra_bits); rest <<= (8-extra_bits); chs[byteSize()] = (char)rest; } String str = new String(chs); fa.visitLdcInsn(str); fa.visitLdcInsn(new Integer(extra_bits)); fa.visitMethodInsn(Opcodes.INVOKESTATIC, EBITSTRING_NAME, "make", "(Ljava/lang/String;I)L" + EBITSTRING_NAME + ";"); return EBITSTRING_TYPE; } /** called by generated code to emit a constant BitString. * If extra > 0, then the last char of str contains the extra bits */ public static EBitString make(String str, int extra) { int in_len = str.length(); byte[] data = new byte[in_len]; for (int i = 0; i < in_len; i++) { data[i] = (byte) str.charAt(i); } int data_len = in_len - (extra>0 ? 1 : 0); return EBitString.make(data, 0, data_len, extra); } @Override public boolean equalsExactly(EObject rhs) { if (rhs.cmp_order() != CMP_ORDER_BITSTRING) return false; EBitString ebs = (EBitString) rhs; if (byteSize() != ebs.byteSize()) return false; if (extra_bits != ebs.extra_bits) return false; int byteOffset = byteOffset(); int ebsByteOffset = ebs.byteOffset(); for (int i = 0; i < byte_size; i++) { if (data[byteOffset + i] != ebs.data[ebsByteOffset + i]) return false; } if (extra_bits != 0) { int myExtraBits = intBitsAt(8L*byte_size, extra_bits); int hisExtraBits = ebs.intBitsAt(8L*byte_size, extra_bits); return myExtraBits == hisExtraBits; } else { return true; } } @Override int compare_same(EObject rhs) { EBitString ebs = (EBitString) rhs; long bc1 = bitSize(); long bc2 = ebs.bitSize(); long limit = Math.min(bc1, bc2); for (int pos = 0; pos < limit; pos += 8) { int rest = limit-pos>8 ? 8 : (int)(limit-pos); int oc1 = 0xff & intBitsAt(pos, rest); int oc2 = 0xff & ebs.intBitsAt(pos, rest); if (oc1 == oc2) continue; if (oc1 < oc2) return -1; if (oc1 > oc2) return 1; } if (bc1 == bc2) return 0; if (bc1 < bc2) return -1; else return 1; } public static EBitString make(byte[] data, int byteOff, int byteLength, int extra_bits) { if (extra_bits == 0) { return new EBinary(data, byteOff, byteLength); } else { return new EBitString(data, byteOff, byteLength, extra_bits); } } public static EBitString makeByteOffsetTail(EBitString org, int byteOff) { return EBitString.make(org.data, org.data_offset+byteOff, org.byte_size-byteOff, org.extra_bits); } protected EBitString(byte[] data, int byte_off, int byte_len, int extra_bits) { this.data = data; this.data_offset = byte_off; this.byte_size = byte_len; this.extra_bits = extra_bits; if (byte_size < 0){ throw new IllegalArgumentException(); } if (data.length < byte_off + byte_len + (extra_bits>0?1:0)) { throw new IllegalArgumentException(); } if (extra_bits<0 || extra_bits>=8) { throw new IllegalArgumentException(); } } public boolean isBinary() { return extra_bits == 0; } public int octetAt(int byteIndex) { return data[byteOffset() + byteIndex] & 0xff; } public int uint16_at(int byteIndex, int flags) { int b1 = data[byteOffset() + byteIndex] & 0xff; int b2 = data[byteOffset() + byteIndex + 1] & 0xff; if ((flags & EBinMatchState.BSF_LITTLE) > 0) return b1 + (b2 << 8); else return (b1 << 8) + b2; } public int int32_at(int byteIndex, int flags) { int b1 = data[byteOffset() + byteIndex] & 0xff; int b2 = data[byteOffset() + byteIndex + 1] & 0xff; int b3 = data[byteOffset() + byteIndex + 2] & 0xff; int b4 = data[byteOffset() + byteIndex + 3] & 0xff; if ((flags & EBinMatchState.BSF_LITTLE) > 0) return b1 + (b2 << 8) + (b3 << 16) + (b4 << 24); else return (b1 << 24) + (b2 << 16) + (b3 << 8) + b4; } public EBitString substring(long bitOff) { return substring(bitOff, bitSize() - bitOff); } public EBitString substring(long bitOff, long bit_len) { if (bitOff < 0 || bitOff + bit_len > bitSize()) { throw new IllegalArgumentException("offset out of range"); } bitOff += byteOffset() * 8; int out_full_bytes = (int) (bit_len/8); int extra = (int) (bit_len%8); if (0 == (bitOff % 8)) { return EBitString.make(data, (int)(bitOff/8), out_full_bytes, extra); } int out_bytes = (int) (out_full_bytes + (extra==0 ? 0 : 1)); byte[] res = new byte[out_bytes]; for (int i = 0; i < out_full_bytes; i++) { res[i] = (byte) intBitsAt(bitOff + i*8, 8); } if (extra != 0) { res[(int) out_full_bytes] = (byte) (intBitsAt(bitOff + bit_len - extra, extra) << (8-extra)); } return EBitString.make(res, 0, (int) out_full_bytes, extra); } public int bitAt(long bitPos) { if (bitPos < 0 || bitPos >= bitSize()) { throw new IllegalArgumentException("bit index out of range"); } bitPos += byteOffset() * 8; int data_byte = (int) data[(int) (bitPos >>> 3)]; int shift = 7 - (int)(bitPos & 0x07); int bit = 0x01 & (data_byte >> shift); return bit; } public int intBitsAt(long bitPos, int bitLength) { if (bitPos + bitLength > this.bitSize()) { throw new IllegalArgumentException( "reading beyond end of BitString"); } if (bitLength < 0 || bitLength > 32) throw new IllegalArgumentException( "this method can only get 32 bits"); bitPos += byteOffset() * 8; int res = 0; // first, get the right-most bits from data[bitPos/8] if ((bitPos & 0x07) != 0) { // how many bits from this byte? int len = 8 - (int)(bitPos & 0x07); // the byte int val = 0x0ff & (int) data[(int) (bitPos >> 3)]; res = val & ((1 << len) - 1); if (bitLength < len) { res >>>= (len - bitLength); return res; } bitLength -= len; bitPos += len; } assert ((bitPos & 0x07) == 0); // we're getting bytes int pos = (int) (bitPos >> 3); while (bitLength > 7) { res <<= 8; res |= 0x0ff & (int) data[pos++]; bitPos += 8; bitLength -= 8; } assert (bitLength < 8); // finally, get the left-most bits from data[bitPos/8] if (bitLength != 0) { // how many bits from this byte? int len = bitLength; // the byte int val = 0x0ff & (int) data[pos]; res <<= bitLength; res |= val >> (8 - len); bitLength -= len; bitPos += len; } assert (bitLength == 0); return res; } public int intLittleEndianBitsAt(long bitPos, int bitLength) { if (bitPos + bitLength > this.bitSize()) { throw new IllegalArgumentException( "reading beyond end of BitString"); } if (bitLength < 0 || bitLength > 32) throw new IllegalArgumentException( "this method can only get 32 bits"); bitPos += byteOffset() * 8; int res = 0; int res_offset = 0; // first, get the left-most bits from data[bitPos/8] if ((bitPos & 0x07) != 0) { // how many bits from this byte? int len = 8 - (int)(bitPos & 0x07); // the byte int val = data[(int) (bitPos >> 3)] & 0x0ff; res = val & ((1 << len) - 1); if (bitLength < len) { res >>>= (len - bitLength); return res; } res_offset += len; bitLength -= len; bitPos += len; } assert ((bitPos & 0x07) == 0); // we're getting bytes int pos = (int) (bitPos >> 3); while (bitLength > 7) { res |= (data[pos++] & 0x0ff) << res_offset; res_offset += 8; bitPos += 8; bitLength -= 8; } assert (bitLength < 8); // finally, get the left-most bits from data[bitPos/8] if (bitLength != 0) { // how many bits from this byte? int len = bitLength; // the byte int val = 0x0ff & (int) data[(int) (bitPos >> 3)]; res |= (val >> (8 - len)) << res_offset; res_offset += len; bitLength -= len; bitPos += len; } assert (bitLength == 0); return res; } /** Sign extend value of size bits. * Assumes that bits above sign are zero. * @see http://www-graphics.stanford.edu/~seander/bithacks.html#VariableSignExtend * */ static int signExtend(int val, int bits) { // Simpler (but slower?): // int nonbits = 32-bits; // return (val << nonbits) >> nonbits; if (bits==32) return val; int r; // resulting sign-extended number int m = 1 << (bits - 1); // mask can be pre-computed if b is fixed // val = val & ((1 << bits) - 1); // (Skip this if bits in x above position b are already zero.) r = (val ^ m) - m; return r; } static long signExtend(long val, int bits) { // Simpler (but slower?): // int nonbits = 64-bits; // return (val << nonbits) >> nonbits; if (bits==64) return val; long r; // resulting sign-extended number long m = 1L << (bits - 1); // mask can be pre-computed if b is fixed // val = val & ((1 << bits) - 1); // (Skip this if bits in x above position b are already zero.) r = (val ^ m) - m; return r; } public byte byteAt(int bitPos) { return (byte) intBitsAt(bitPos, 8); } public double doubleAt(int bitPos) { return Double.longBitsToDouble(longBitsAt(bitPos, 64)); } public double floatAt(int bitPos) { return Float.intBitsToFloat(intBitsAt(bitPos, 32)); } public long longBitsAt(long bitPos, int bitLength) { if (bitPos + bitLength > this.bitSize()) { throw new IllegalArgumentException( "reading beyond end of BitString"); } if (bitLength < 0 || bitLength > 64) throw new IllegalArgumentException( "this method can only get 64 bits"); long res = 0; bitPos += byteOffset() * 8; // first, get the right-most bits from data[bitPos/8] if ((bitPos & 0x07) != 0) { // how many bits from this byte? int len = 8 - (int)(bitPos & 0x07); // the byte int val = 0x0ff & (int) data[(int) (bitPos >> 3)]; res = val & ((1 << len) - 1); // are we looking for less that len bits? if (bitLength < len) { res >>>= (len - bitLength); return res; } bitLength -= len; bitPos += len; } assert ((bitPos & 0x07) == 0); // we're getting bytes int pos = (int) (bitPos >> 3); while (bitLength > 7) { res <<= 8; res |= 0x0ff & (int) data[pos++]; bitPos += 8; bitLength -= 8; } assert (bitLength < 8); assert ((bitPos & 0x07) == 0); // finally, get the left-most bits from data[bitPos/8] if (bitLength != 0) { // how many bits from this byte? int len = bitLength; // the byte int val = 0x0ff & (int) data[(int) (bitPos >> 3)]; res <<= bitLength; res |= val >> (8 - len); bitLength -= len; bitPos += len; } assert (bitLength == 0); return res; } public long longLittleEndianBitsAt(long bitPos, int bitLength) { if (bitPos + bitLength > this.bitSize()) { throw new IllegalArgumentException( "reading beyond end of BitString"); } if (bitLength < 0 || bitLength > 64) throw new IllegalArgumentException( "this method can only get 64 bits"); long res = 0; int res_offset = 0; bitPos += byteOffset() * 8; // first, get the right-most bits from data[bitPos/8] if ((bitPos & 0x07) != 0) { // how many bits from this byte? int len = 8 - (int)(bitPos & 0x07); // the byte int val = data[(int) (bitPos >> 3)] & 0x0ff; res = val & ((1 << len) - 1); // are we looking for less that len bits? if (bitLength < len) { res >>>= (len - bitLength); return res; } res_offset += len; bitLength -= len; bitPos += len; } assert ((bitPos & 0x07) == 0); // we're getting bytes int pos = (int) (bitPos >> 3); while (bitLength >= 8) { res |= ((long)(data[pos++] & 0x0ff)) << res_offset; res_offset += 8; bitPos += 8; bitLength -= 8; } assert (bitLength < 8); assert ((bitPos & 0x07) == 0); // finally, get the left-most bits from data[bitPos/8] if (bitLength != 0) { // how many bits from this byte? int len = bitLength; // the byte int val = data[pos] & 0x0ff; res |= (long)(val >> (8 - len)) << res_offset; res_offset += len; bitLength -= len; bitPos += len; } assert (bitLength == 0); return res; } public EObject toList() { EObject res = ERT.NIL; int extra = (int) ((bitSize() % 8)); long pos; if ((bitSize() % 8) == 0) { pos = bitSize() - 8; } else { res = res.cons( substring(bitSize() - extra) ); pos = bitSize() - extra - 8; } while (pos >= 0) { res = res.cons(ERT.box(octetAt((int)(pos/8)))); pos -= 8; } return res; } @Override public String toString() { foo: if (extra_bits == 0) { StringBuilder sb = new StringBuilder("<<\""); for (int i = 0; i < bitSize(); i += 8) { char ch = (char) (0xff & intBitsAt(i, 8)); if (ch < ' ' || ch > '~') break foo; sb.append((char) ch); } sb.append("\">>"); return sb.toString(); } StringBuilder sb = new StringBuilder("<<"); int i = 0; long max = Math.min(bitSize()-8, 20*8); for (; i < max; i += 8) { sb.append(0xff & intBitsAt(i, 8)); sb.append(','); } if (max != bitSize()-8) { sb.append("...,"); i = (int) (bitSize()-8); } int lastBitLength = (int) (bitSize() - i); sb.append(0xff & intBitsAt(i, lastBitLength)); if (lastBitLength != 8) { sb.append(':').append(lastBitLength); } sb.append(">>"); return sb.toString(); } /** * @return */ public byte[] toByteArray() { byte[] result = new byte[dataByteSize()]; System.arraycopy(data, byteOffset(), result, 0, dataByteSize()); return result; } @Override public void visitIOList(EIOListVisitor out) throws ErlangError { if (byteSize() > 0) { out.visit(data, byteOffset(), byteSize()); } if (extra_bits != 0) { long bitsPos = (8 * byteSize()); int bits = (0xff & intBitsAt(bitsPos, extra_bits)); out.visitBits( bits, extra_bits ); } } /** * @return true if successful */ @Override public boolean collectIOList(List<ByteBuffer> out) { if (extra_bits != 0) return false; if (byteSize() > 0) { out.add(ByteBuffer.wrap(data, byteOffset(), byteSize())); } return true; } /** * @param eInputStream * @return */ public static EBitString read(EInputStream eInputStream) throws IOException { int[] pad_bits = new int[1]; byte[] data = eInputStream.read_bitstr(pad_bits); int extra_bits = 8 - pad_bits[0]; if (extra_bits==8) { return new EBinary(data); //TODO: use a make(). } else { int len = data.length-1; return make(data, 0, data.length-1, extra_bits); } } protected int byteOffset() { return data_offset; } public int byteSize() { return byte_size; } public int totalByteSize() { return byte_size + (extra_bits>0 ? 1 : 0); } @Override public ESeq collectCharList(CharCollector out, ESeq rest) throws CharCollector.CollectingException, CharCollector.InvalidElementException, IOException { if (extra_bits != 0) throw new CharCollector.InvalidElementException(); try { return out.addBinary(data, data_offset, byte_size, rest); } catch (CharCollector.PartialDecodingException e) { int n = e.inputPos; throw new CharCollector.CollectingException(EBitString.make(data, data_offset+n, byte_size-n, extra_bits)); } } @Override public void encode(EOutputStream eos) { // TODO: Avoid copying. eos.write_bitstr(toByteArray(), extra_bits==0 ? 0 : 8-extra_bits); } @Override public ETermPattern compileMatch(Set<Integer> out) { return EPattern.compilePattern(this, out); } }