/** * 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.nio.ByteBuffer; import java.io.IOException; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; /** * Special cons cell optimizing for [byte, byte, byte | Tail]. * An <code>EBinList</code> has a tail, and an array of bytes for the * values it contains. It is optimized for the case of cons'ing a * byte onto it; it works like a java StringBuilder, but its contents * array grows backwards; so the bytes contained in it are stored in * the last bytes of the <code>data</code> array variable. */ public class EStringList extends ESeq { static class PrependableBytes { AtomicInteger start_pos; byte[] data; PrependableBytes(int size) { this.data = new byte[size]; this.start_pos = new AtomicInteger(data.length); } PrependableBytes(int size, byte v) { this.data = new byte[size]; data[size-1] = v; this.start_pos = new AtomicInteger(data.length-1); } PrependableBytes(byte[] org, int extra) { this(org, 0, org.length, extra); } PrependableBytes(byte[] org, int off, int len, int extra) { this(org.length + extra); System.arraycopy(org,off, data,data.length-len, len); } PrependableBytes prepend(int old_start, byte b) { assert (old_start > 0); int new_start = old_start-1; if (start_pos.compareAndSet(old_start, new_start)) { data[new_start] = b; return this; } else return null; } boolean prepend2(int old_start, byte b) { assert (old_start > 0); int new_start = old_start-1; if (start_pos.compareAndSet(old_start, new_start)) { data[new_start] = b; return true; } else return false; } PrependableBytes prepend(int old_start, byte[] org, int off, int len) { assert (old_start > 0); int new_start = old_start-len; if (start_pos.compareAndSet(old_start, new_start)) { System.arraycopy(org,off, data,new_start, len); return this; } else return null; } } /** * */ private static final int INITIAL_BUFFER_SIZE = 10; final PrependableBytes bytes; final int off; final int len; final ESeq tail; private EStringList(PrependableBytes bytes, int off, int len, ESeq tail) { assert len>0; this.bytes = bytes; this.off = off; this.len = len; this.tail = tail; } /** create a list with [value|tail], where value is a smallint 0..255 */ public EStringList(byte value, ESeq tail) { this(new PrependableBytes(INITIAL_BUFFER_SIZE, value), INITIAL_BUFFER_SIZE-1, 1, tail); } public EStringList(byte[] header, ESeq tail) { this(new PrependableBytes(header, INITIAL_BUFFER_SIZE), INITIAL_BUFFER_SIZE, header.length, tail); } public ECons testNonEmptyList() { if (len == 0) return tail.testNonEmptyList(); return this; } public ESeq testSeq() { return this; } @Override public ESeq cons(EObject h) { ESmall sm = h.testSmall(); if (sm != null) { int value = sm.value; if ((value & ~0xff) == 0) { // Fits in a byte if (off==0) return new EStringList((byte)value, this); if (bytes.prepend2(this.off, (byte)value)) { return new EStringList(bytes, off-1, len+1, tail); } else { return new EStringList((byte)value, this); } } } return new EList(h, this); } public EObject drop(int n) { if (n > len) throw new IllegalArgumentException(); if (n == len) return tail; return new EStringList(bytes, off + n, len - n, tail); } @Override public ESmall head() { return ESmall.little[(bytes.data[off] & 0xff)]; } @Override public ESeq tail() { if (len == 1) return tail; return new EStringList(bytes, off + 1, len - 1, tail); } // TODO: Remove this method altogether @Override public boolean isNil() { assert len != 0; return false; } @Override public ENil testNil() { if (isNil()) return ERT.NIL; return null; } public EString testString() { EString st = tail.testString(); if (st == null) { return null; } //TODO: handle stringlist chain more efficiently? byte[] out_bin = new byte[len + st.length()]; System.arraycopy(this.bytes.data, this.off, out_bin, 0, this.len); System.arraycopy(st.data, st.off, out_bin, len, st.length()); return new EString(out_bin, 0); } private ESeq seq() { return new Seq(); } /** * Helper class that looks at this EBinList as a Seq. */ private class Seq extends ESeq { @Override public ECons testNonEmptyList() { return EStringList.this.testNonEmptyList(); } @Override public ESeq cons(EObject h) { return EStringList.this.cons(h); } @Override public ESeq tail() { return EStringList.this.tail(); } @Override public EObject head() { return EStringList.this.head(); } @Override public void encode(EOutputStream eos) { EStringList.this.encode(eos); } } private boolean all_printable() { byte val; for (int i = 0; i < len; i++) { val = bytes.data[off+i]; if (val < ' ' || val >= 127) { return false; } } return true; } @Override public String toString() { // // Can this be printed as "..." // if (tail.isNil() && all_printable()) { StringBuilder sb = new StringBuilder("\""); for (int i = 0; i < len; i++) { byte val = bytes.data[off+i]; sb.append((char)val); } sb.append('"'); return sb.toString(); } // // Otherwise, print it as [.., .., |..] // StringBuilder sb = new StringBuilder("["); int max = Math.min(len, 40); for (int i = 0; i < max; i++) { if (i != 0) { sb.append(","); } byte val = bytes.data[off+i]; if (val > ' ' && val < 127) { sb.append('$'); sb.append((char)val); } else { sb.append(val & 0xFF); } } if (max!=len) { sb.append("..."); } if (!tail.isNil()) { sb.append('|'); sb.append(tail); } sb.append("]"); return sb.toString(); } @Override public void visitIOList(EIOListVisitor out) throws ErlangError { out.visit(bytes.data, off, len); //? tail.visitIOList(out); } @Override public boolean collectIOList(List<ByteBuffer> out) { out.add(ByteBuffer.wrap(bytes.data, off, len)); //? return tail.collectIOList(out); } @Override public ESeq collectCharList(CharCollector out, ESeq rest) throws CharCollector.CollectingException, CharCollector.InvalidElementException, IOException { try { rest = out.addBinary(bytes.data, off, len, rest); } catch (CharCollector.PartialDecodingException e) { throw new CharCollector.CollectingException(drop(e.inputPos - off)); } if (tail.testNumber() != null) { // Only nil and binaries are allowed as tail // TODO: Fail sooner? throw new CharCollector.InvalidElementException(); } else { return tail.collectCharList(out, rest); } } @Override public void encode(EOutputStream eos) { if (tail.isNil()) { eos.write_string(bytes.data, off, len); } else { //TODO: Check whether tail is EString or EStringList eos.write_list_head(len); for (int i = 0; i < len; i++) { eos.write_int(bytes.data[off+i]); } eos.write_any(tail); } } public static EStringList fromString(String c, ESeq tail) { byte[] data = new byte[c.length()]; for (int i = 0; i < data.length; i++) { data[i] = (byte) c.charAt(i); } return new EStringList(data, tail); } private static final Type ESTRINGLIST_TYPE = Type.getType(EStringList.class); private static final Type STRING_TYPE = Type.getType(String.class); private static final String ESEQ_DESC = Type.getDescriptor(ESeq.class); @Override public Type emit_const(MethodVisitor fa) { Type type = ESTRINGLIST_TYPE; char[] ch = new char[len]; for (int i = 0; i < len; i++) { ch[i] = (char)(0xff & (int)bytes.data[off+i]); } fa.visitLdcInsn(new String(ch)); tail.emit_const(fa); fa.visitMethodInsn(Opcodes.INVOKESTATIC, type.getInternalName(), "fromString", "(" + STRING_TYPE.getDescriptor() + ESEQ_DESC + ")" + type.getDescriptor()); return type; } }