// Copyright 2011, 2012 Tony Garnock-Jones . // // This file is part of Hop. // // Hop is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Hop is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public // License for more details. // // You should have received a copy of the GNU General Public License // along with Hop. If not, see . // package hop; import java.io.IOException; import java.io.InputStream; public class SexpReader { public InputStream _input; public SexpReader(InputStream input) { _input = input; } /** * Reads a sexp length-prefix from _input. * @return The read length, or -1 if the end of stream is reached * @throws IOException * @throws SexpSyntaxError */ public int _readLength(int lengthSoFar) throws IOException { int length = lengthSoFar; while (true) { int c = _input.read(); if (c == -1) return -1; if (c == ':') { return length; } if (!Character.isDigit(c)) { throw new SexpSyntaxError("Invalid length prefix"); } length = length * 10 + (c - '0'); } } /** * Reads a simple length-prefixed string from _input, given either zero or the value * of the first digit of the length-prefix being read. * @param lengthSoFar either zero or the first digit of the length prefix to use * @return the read string * @throws IOException * @throws SexpSyntaxError */ public byte[] _readSimpleString(int lengthSoFar) throws IOException { int length = _readLength(lengthSoFar); if (length == -1) return null; byte[] buf = new byte[length]; int offset = 0; while (length > 0) { int count = _input.read(buf, offset, length); if (count == -1) { throw new SexpSyntaxError("End-of-stream in the middle of a simple string"); } offset += count; length -= count; } return buf; } public byte[] readSimpleString() throws IOException { return _readSimpleString(0); } public SexpList _readList() throws IOException { SexpList list = new SexpList(); while (true) { int c = _input.read(); switch (c) { case -1: throw new SexpSyntaxError("Unclosed list"); case ')': return list; default: list.add(_read(c)); break; } } } public Object _read(int c) throws IOException { while (true) { switch (c) { case -1: return null; case '(': return _readList(); case ')': throw new SexpSyntaxError("Unexpected close-paren"); case '[': byte[] hint = readSimpleString(); switch (_input.read()) { case -1: throw new SexpSyntaxError("End-of-stream between display hint and body"); case ']': break; default: throw new SexpSyntaxError("Unexpected character after display hint"); } byte[] body = readSimpleString(); return new SexpDisplayHint(hint, body); default: if (Character.isDigit(c)) { return new SexpBytes(_readSimpleString(c - '0')); } else if (Character.isWhitespace(c)) { // Skip harmless (?) whitespace c = _input.read(); continue; } else { throw new SexpSyntaxError("Unexpected character: " + c); } } } } public Object read() throws IOException { return _read(_input.read()); } public SexpList readList() throws IOException { Object x = read(); if (x != null && !(x instanceof SexpList)) { throw new SexpSyntaxError("Unexpected non-list"); } return (SexpList) x; } public SexpBytes readBytes() throws IOException { Object x = read(); if (x != null && !(x instanceof SexpBytes)) { throw new SexpSyntaxError("Unexpected non-bytes"); } return (SexpBytes) x; } }