package org.jerlang.stdlib.beam_lib;
import java.io.DataInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collections;
import org.jerlang.erts.ExternalTermFormatTag;
import org.jerlang.stdlib.Lists;
import org.jerlang.type.Atom;
import org.jerlang.type.Binary;
import org.jerlang.type.BitString;
import org.jerlang.type.Float;
import org.jerlang.type.Integer;
import org.jerlang.type.List;
import org.jerlang.type.Map;
import org.jerlang.type.Str;
import org.jerlang.type.Term;
import org.jerlang.type.Tuple;
public class ExternalTermReader extends AbstractReader {
public ExternalTermReader(DataInputStream inputStream) {
super(inputStream);
}
public Term read() throws Throwable {
int versionOrTag = read1Byte();
int tagId;
if (versionOrTag == 131) {
tagId = read1Byte();
} else {
tagId = versionOrTag;
}
// TODO: Use java.lang.invoke.MethodHandle instead of switch
ExternalTermFormatTag tag = ExternalTermFormatTag.of(tagId);
switch (tag) {
case ATOM_EXT:
return readAtom();
case BINARY_EXT:
return readBinary();
case BIT_BINARY_EXT:
return readBitBinary();
case BIT_BINARY_INTERNAL_REF:
read4Bytes(); // offset
read4Bytes(); // size
return new Str("Binary Internal Ref");
case INTEGER_EXT:
return Integer.of(read4Bytes());
case LIST_EXT:
return readList();
case MAP_EXT:
return readMap();
case NEW_FLOAT_EXT:
return readNewFloat();
case NIL_EXT:
return List.nil;
case SMALL_BIG_EXT:
return readSmallBig();
case SMALL_INTEGER_EXT:
return Integer.of(read1Byte());
case SMALL_TUPLE_EXT:
return readSmallTuple();
case STRING_EXT:
return readString();
default:
throw new Error("Tag " + tag + " not supported yet");
}
}
private Term readBitBinary() throws IOException {
byte[] bytes = new byte[read4Bytes()];
int usedBits = read1Byte();
readBytes(bytes);
// TODO: return Binary if usedBits % 8 = 0
return new BitString(bytes, 8 - usedBits);
}
private Term readMap() throws Throwable {
int pairs = read4Bytes();
Map map = new Map();
while (pairs-- > 0) {
map.set(read(), read());
}
return map;
}
private Term readNewFloat() throws IOException {
return new Float(Double.longBitsToDouble(read8Bytes()));
}
private Atom readAtom() throws IOException {
byte[] bytes = new byte[read2Bytes()];
readBytes(bytes);
return Atom.of(bytes);
}
private Binary readBinary() throws IOException {
byte[] bytes = new byte[read4Bytes()];
readBytes(bytes);
return new Binary(bytes);
}
private List readList() throws Throwable {
int length = read4Bytes();
List list = List.nil;
while (length-- > 0) {
list = new List(read(), list);
}
read(); // Lists are terminated by a NIL, which we skip
return Lists.reverse(list);
}
private Term readSmallBig() throws IOException {
int n = read1Byte();
read1Byte(); // TODO: int sign = read1Byte();
byte[] d = new byte[n];
readBytes(d);
Collections.reverse(Arrays.asList(d));
return new Integer(new BigInteger(d));
}
private Term readSmallTuple() throws Throwable {
int arity = read1Byte();
Term[] terms = new Term[arity];
for (int index = 0; index < arity; index++) {
terms[index] = read();
}
return Tuple.of(terms);
}
private Str readString() throws IOException {
int len = read2Bytes();
byte[] bytes = new byte[len];
readBytes(bytes);
return Str.of(new String(bytes));
}
/*
* Reverses the bytes in the array in-place.
*
* Using `Collections.reverse(Arrays.asList(array))` is not possible,
* because that does not work on arrays of primitives.
*/
public static void reverse(byte[] array) {
int half = array.length >> 1;
for (int i = 0, j = array.length - 1; i < half; i++, j--) {
array[i] ^= array[j];
array[j] ^= array[i];
array[i] ^= array[j];
}
}
}