/*******************************************************************************
* Copyright (c) INRIA-LORIA and CWI 2006-2009
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Jurgen Vinju (jurgenv@cwi.nl) - initial API and implementation
*******************************************************************************/
package org.rascalmpl.value.io;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import org.rascalmpl.value.IConstructor;
import org.rascalmpl.value.IListWriter;
import org.rascalmpl.value.IMapWriter;
import org.rascalmpl.value.ISetWriter;
import org.rascalmpl.value.ITuple;
import org.rascalmpl.value.IValue;
import org.rascalmpl.value.IValueFactory;
import org.rascalmpl.value.exceptions.FactParseError;
import org.rascalmpl.value.exceptions.IllegalOperationException;
import org.rascalmpl.value.exceptions.UndeclaredAbstractDataTypeException;
import org.rascalmpl.value.type.Type;
import org.rascalmpl.value.type.TypeFactory;
import org.rascalmpl.value.type.TypeStore;
// TODO: add support for values of type Value, for this we need overloading resolving
public class ATermReader extends AbstractBinaryReader {
private IValueFactory vf;
private TypeFactory tf = TypeFactory.getInstance();
private TypeStore ts;
public IValue read(IValueFactory factory, TypeStore store, Type type, InputStream stream)
throws FactParseError, IOException {
this.vf = factory;
this.ts = store;
int firstToken;
do {
firstToken = stream.read();
if (firstToken == -1) {
throw new IOException("Premature EOF.");
}
} while (Character.isWhitespace((char) firstToken));
char typeByte = (char) firstToken;
if (typeByte == '!') {
SharingStream sreader = new SharingStream(stream);
sreader.initializeSharing();
sreader.readSkippingWS();
return parse(sreader, type);
} else if (typeByte == '?') {
throw new UnsupportedOperationException("nyi");
} else if (Character.isLetterOrDigit(typeByte) || typeByte == '_'
|| typeByte == '[' || typeByte == '-') {
SharingStream sreader = new SharingStream(stream);
sreader.last_char = typeByte;
return parse(sreader, type);
} else {
throw new RuntimeException("nyi");
}
}
// TODO add support for anonymous constructors (is already done for the parseNumber case)
private IValue parse(SharingStream reader, Type expected)
throws IOException {
IValue result;
int start, end;
start = reader.getPosition();
switch (reader.getLastChar()) {
case -1:
throw new FactParseError("premature EOF encountered.", start);
case '#':
return parseAbbrev(reader);
case '[':
result = parseList(reader, expected);
break;
case '<':
throw new FactParseError("Placeholders are not supported", start);
case '"':
result = parseString(reader, expected);
break;
case '(':
result = parseTuple(reader, expected);
break;
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
result = parseNumber(reader, expected);
break;
default:
result = parseAppl(reader, expected);
}
if (reader.getLastChar() == '{') {
result = parseAnnotations(reader, result);
}
end = reader.getPosition();
reader.storeNextTerm(result, end - start);
return result;
}
private IValue parseAnnotations(SharingStream reader, IValue result)
throws IOException {
if (reader.readSkippingWS() == '}') {
reader.readSkippingWS();
} else {
result = parseAnnos(reader, result);
if (reader.getLastChar() != '}') {
throw new FactParseError("'}' expected", reader.getPosition());
}
}
return result;
}
private IValue parseAppl(SharingStream reader, Type expected)
throws IOException {
int c;
IValue result;
c = reader.getLastChar();
if (Character.isLetter(c)) {
String funname = parseId(reader);
Type node;
if (expected.isAbstractData()) {
Set<Type> nodes = ts.lookupConstructor(expected, funname);
// TODO deal with overloading
Iterator<Type> iterator = nodes.iterator();
if (!iterator.hasNext()) {
throw new UndeclaredAbstractDataTypeException(expected);
}
node = iterator.next();
}
else {
node = expected;
}
c = reader.skipWS();
if (reader.getLastChar() == '(') {
c = reader.readSkippingWS();
if (c == -1) {
throw new FactParseError("premature EOF encountered.", reader.getPosition());
}
if (reader.getLastChar() == ')') {
// result = node.make(vf, ts, funname, new IValue[0]);
if(node.isTop()) {
Type constr = ts.lookupFirstConstructor(funname, tf.tupleType(new Type[0]));
if(constr != null) node = constr;
}
if(node.isConstructor())
result = vf.constructor(node, new IValue[0]);
else
result = vf.node(funname, new IValue[0]);
} else {
IValue[] list;
if (expected.isAbstractData()) {
list = parseFixedSizeATermsArray(reader, node.getFieldTypes());
}
else {
list = parseATermsArray(reader, TypeFactory.getInstance().valueType());
}
if (reader.getLastChar() != ')') {
throw new FactParseError("expected ')' but got '"
+ (char) reader.getLastChar() + "'", reader.getPosition());
}
if(node.isTop()) {
Type constr = ts.lookupFirstConstructor(funname, tf.tupleType(list));
if(constr != null) node = constr;
}
if (node.isConstructor())
result = vf.constructor(node, list);
else
result = vf.node(funname, list);
}
c = reader.readSkippingWS();
} else {
if (node.isConstructor()) {
result = vf.constructor(node);
}
else {
result = vf.node(funname);
}
}
} else {
throw new FactParseError("illegal character: "
+ (char) reader.getLastChar(), reader.getPosition());
}
return result;
}
private IValue parseTuple(SharingStream reader, Type expected)
throws IOException {
int c;
IValue result;
c = reader.readSkippingWS();
if (c == -1) {
throw new FactParseError("premature EOF encountered.", reader.getPosition());
}
if (reader.getLastChar() == ')') {
result = vf.tuple();
} else {
IValue[] list = parseFixedSizeATermsArray(reader, expected);
if (reader.getLastChar() != ')') {
throw new FactParseError("expected ')' but got '"
+ (char) reader.getLastChar() + "'", reader.getPosition());
}
result = vf.tuple(list);
}
c = reader.readSkippingWS();
return result;
}
private IValue parseString(SharingStream reader, Type expected) throws IOException {
int c;
IValue result;
String str = parseStringLiteral(reader);
// note that we interpret all strings as strings, not possible function names.
// this deviates from the ATerm library.
result = vf.string(str);
c = reader.readSkippingWS();
if (c == -1) {
throw new FactParseError("premature EOF encountered.", reader.getPosition());
}
return result;
}
private IValue parseList(SharingStream reader, Type expected)
throws IOException {
IValue result;
int c;
c = reader.readSkippingWS();
if (c == -1) {
throw new FactParseError("premature EOF encountered.", reader.getPosition());
}
if (c == ']') {
c = reader.readSkippingWS();
if (expected.isList()) {
result = vf.list(expected.getElementType());
} else if (expected.equivalent(TypeFactory.getInstance().valueType())) {
result = vf.list(tf.valueType());
}
else {
throw new FactParseError("Did not expect a list, rather a "
+ expected, reader.getPosition());
}
} else {
result = parseATerms(reader, expected);
if (reader.getLastChar() != ']') {
throw new FactParseError("expected ']' but got '"
+ (char) reader.getLastChar() + "'", reader.getPosition());
}
c = reader.readSkippingWS();
}
return result;
}
private IValue parseAnnos(SharingStream reader, IValue result) throws IOException {
result = parseAnno(reader, result);
while (reader.getLastChar() == ',') {
reader.readSkippingWS();
result = parseAnno(reader, result);
}
return result;
}
private IValue parseAnno(SharingStream reader, IValue result) throws IOException {
if (reader.getLastChar() == '[') {
int c = reader.readSkippingWS();
if (c == '"') {
String key = parseStringLiteral(reader);
Type annoType = ts.getAnnotationType(result.getType(), key);
if (reader.readSkippingWS() == ',') {
reader.readSkippingWS();
IValue value = parse(reader, annoType);
if (result.getType().isAbstractData()) {
result = ((IConstructor) result).asAnnotatable().setAnnotation(key, value);
}
if (reader.getLastChar() != ']') {
throw new FactParseError("expected a ] but got a " + reader.getLastChar(), reader.getPosition());
}
reader.readSkippingWS();
return result;
}
throw new FactParseError("expected a comma before the value of the annotation", reader.getPosition());
}
throw new FactParseError("expected a label for an annotation", reader.getPosition());
}
// no annotations
return result;
}
static private boolean isBase64(int c) {
return Character.isLetterOrDigit(c) || c == '+' || c == '/';
}
private IValue parseAbbrev(SharingStream reader) throws IOException {
IValue result;
int abbrev;
int c = reader.read();
abbrev = 0;
while (isBase64(c)) {
abbrev *= 64;
if (c >= 'A' && c <= 'Z') {
abbrev += c - 'A';
} else if (c >= 'a' && c <= 'z') {
abbrev += c - 'a' + 26;
} else if (c >= '0' && c <= '9') {
abbrev += c - '0' + 52;
} else if (c == '+') {
abbrev += 62;
} else if (c == '/') {
abbrev += 63;
} else {
throw new RuntimeException("not a base-64 digit: " + c);
}
c = reader.read();
}
result = reader.getTerm(abbrev);
return result;
}
private IValue parseNumber(SharingStream reader, Type expected) throws IOException {
StringBuilder str = new StringBuilder();
IValue result;
do {
str.append((char) reader.getLastChar());
} while (Character.isDigit(reader.read()));
if (reader.getLastChar() != '.' && reader.getLastChar() != 'e'
&& reader.getLastChar() != 'E' && reader.getLastChar() != 'l'
&& reader.getLastChar() != 'L') {
int val;
try {
val = Integer.parseInt(str.toString());
} catch (NumberFormatException e) {
throw new FactParseError("malformed int:" + str, reader.getPosition());
}
result = vf.integer(val);
} else if (reader.getLastChar() == 'l' || reader.getLastChar() == 'L') {
reader.read();
throw new FactParseError("No support for longs", reader.getPosition());
} else {
if (reader.getLastChar() == '.') {
str.append('.');
reader.read();
if (!Character.isDigit(reader.getLastChar()))
throw new FactParseError("digit expected", reader.getPosition());
do {
str.append((char) reader.getLastChar());
} while (Character.isDigit(reader.read()));
}
if (reader.getLastChar() == 'e' || reader.getLastChar() == 'E') {
str.append((char) reader.getLastChar());
reader.read();
if (reader.getLastChar() == '-' || reader.getLastChar() == '+') {
str.append((char) reader.getLastChar());
reader.read();
}
if (!Character.isDigit(reader.getLastChar()))
throw new FactParseError("digit expected!", reader.getPosition());
do {
str.append((char) reader.getLastChar());
} while (Character.isDigit(reader.read()));
}
double val;
try {
val = Double.valueOf(str.toString());
result = vf.real(val);
} catch (NumberFormatException e) {
throw new FactParseError("malformed real", reader.getPosition(), e);
}
}
reader.skipWS();
return result;
}
private String parseId(SharingStream reader) throws IOException {
int c = reader.getLastChar();
StringBuilder buf = new StringBuilder(32);
do {
buf.append((char) c);
c = reader.read();
} while (Character.isLetterOrDigit(c) || c == '_' || c == '-'
|| c == '+' || c == '*' || c == '$' || c == '.');
// slight deviation here, allowing . inside of identifiers
return buf.toString();
}
private String parseStringLiteral(SharingStream reader) throws IOException {
boolean escaped;
StringBuilder str = new StringBuilder();
do {
escaped = false;
if (reader.read() == '\\') {
reader.read();
escaped = true;
}
int lastChar = reader.getLastChar();
if(lastChar == -1) throw new IOException("Premature EOF.");
if (escaped) {
switch (lastChar) {
case 'n':
str.append('\n');
break;
case 't':
str.append('\t');
break;
case 'b':
str.append('\b');
break;
case 'r':
str.append('\r');
break;
case 'f':
str.append('\f');
break;
case '\\':
str.append('\\');
break;
case '\'':
str.append('\'');
break;
case '\"':
str.append('\"');
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
str.append(reader.readOct());
break;
default:
str.append('\\').append((char) lastChar);
}
} else if (lastChar != '\"'){
str.append((char) lastChar);
}
} while (escaped || reader.getLastChar() != '"');
return str.toString();
}
private IValue parseATerms(SharingStream reader, Type expected)
throws IOException {
Type base = expected;
Type elementType = getElementType(expected);
IValue[] terms = parseATermsArray(reader, elementType);
if (base.isList() || base.equivalent(tf.valueType())) {
IListWriter w = vf.listWriter();
for (int i = terms.length - 1; i >= 0; i--) {
w.insert(terms[i]);
}
return w.done();
} else if (base.isSet()) {
ISetWriter w = vf.setWriter();
w.insert(terms);
return w.done();
} else if (base.isMap()) {
IMapWriter w = vf.mapWriter();
for (IValue elem : terms) {
ITuple tuple = (ITuple) elem;
w.put(tuple.get(0), tuple.get(1));
}
return w.done();
} else if (base.isRelation()) {
ISetWriter w = vf.setWriter();
w.insert(terms);
return w.done();
}
throw new FactParseError("Unexpected type " + expected, reader.getPosition());
}
private Type getElementType(Type expected) {
Type base = expected;
if (base.isList()) {
return base.getElementType();
} else if (base.isSet()) {
return base.getElementType();
} else if (base.isMap()) {
return tf.tupleType(base.getKeyType(), base.getValueType());
} else if (base.isRelation()) {
return base.getFieldTypes();
} else if (base.isTop()) {
return base;
}
else {
throw new IllegalOperationException("getElementType", expected);
}
}
private IValue[] parseATermsArray(SharingStream reader,
Type elementType) throws IOException {
List<IValue> list = new ArrayList<>(2);
IValue term = parse(reader, elementType);
list.add(term);
while (reader.getLastChar() == ',') {
reader.readSkippingWS();
term = parse(reader, elementType);
list.add(term);
}
IValue[] array = new IValue[list.size()];
ListIterator<IValue> iter = list.listIterator();
int index = 0;
while (iter.hasNext()) {
array[index++] = iter.next();
}
return array;
}
private IValue[] parseFixedSizeATermsArray(SharingStream reader,
Type elementTypes) throws IOException {
List<IValue> list = new ArrayList<>(elementTypes.getArity());
int i = 0;
Type elementType = elementTypes.getFieldType(i++);
IValue term = parse(reader, elementType);
list.add(term);
while (reader.getLastChar() == ',') {
elementType = elementTypes.getFieldType(i++);
reader.readSkippingWS();
term = parse(reader, elementType);
list.add(term);
}
IValue[] array = new IValue[list.size()];
ListIterator<IValue> iter = list.listIterator();
int index = 0;
while (iter.hasNext()) {
array[index++] = iter.next();
}
return array;
}
private static class SharingStream {
private static final int INITIAL_TABLE_SIZE = 2048;
private static final int TABLE_INCREMENT = 4096;
private static final int INITIAL_BUFFER_SIZE = 1024;
private InputStream reader;
int last_char;
private int pos;
private int nr_terms;
private IValue[] table;
private byte[] buffer;
private int limit;
private int bufferPos;
public SharingStream(InputStream reader) {
this(reader, INITIAL_BUFFER_SIZE);
}
public SharingStream(InputStream stream, int bufferSize) {
this.reader = stream;
last_char = -1;
pos = 0;
if (bufferSize < INITIAL_BUFFER_SIZE)
buffer = new byte[bufferSize];
else
buffer = new byte[INITIAL_BUFFER_SIZE];
limit = -1;
bufferPos = -1;
}
public void initializeSharing() {
table = new IValue[INITIAL_TABLE_SIZE];
nr_terms = 0;
}
public void storeNextTerm(IValue t, int size) {
if (table == null) {
return;
}
if (nr_terms == table.length) {
IValue[] new_table = new IValue[table.length + TABLE_INCREMENT];
System.arraycopy(table, 0, new_table, 0, table.length);
table = new_table;
}
table[nr_terms++] = t;
}
public IValue getTerm(int index) {
if (index < 0 || index >= nr_terms) {
throw new RuntimeException("illegal index");
}
return table[index];
}
public int read() throws IOException {
if (bufferPos == limit) {
limit = reader.read(buffer);
bufferPos = 0;
}
if (limit == -1) {
last_char = -1;
} else {
last_char = buffer[bufferPos++];
pos++;
}
return last_char;
}
public int readSkippingWS() throws IOException {
do {
last_char = read();
} while (Character.isWhitespace(last_char));
return last_char;
}
public int skipWS() throws IOException {
while (Character.isWhitespace(last_char)) {
last_char = read();
}
return last_char;
}
public int readOct() throws IOException {
int val = Character.digit(last_char, 8);
val += Character.digit(read(), 8);
if (val < 0) {
throw new FactParseError("octal must have 3 octdigits.", getPosition());
}
val += Character.digit(read(), 8);
if (val < 0) {
throw new FactParseError("octal must have 3 octdigits", getPosition());
}
return val;
}
public int getLastChar() {
return last_char;
}
public int getPosition() {
return pos;
}
}
}