/*
* Copyright (C) 2014 James Lawrence.
*
* This file is part of LibLab.
*
* LibLab 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sqrt.liblab.io;
import com.sqrt.liblab.threed.Angle;
import com.sqrt.liblab.threed.Vector3f;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
public class TextParser {
private final DataSource source;
public final List<String> lines = new LinkedList<String>();
private int idx, charIdx;
private String line;
public TextParser(DataSource dataSource) throws IOException {
this.source = dataSource;
while(dataSource.remaining() > 0) {
String line = dataSource.getLine();
line = line.trim();
if(line.isEmpty() || line.charAt(0) == '#')
continue;
lines.add(line);
}
line = lines.isEmpty()? null: lines.get(idx++);
charIdx = 0;
}
private void error(String message) throws IOException {
StringBuilder footer = new StringBuilder("\t").append(line).append("\n\t");
for(int i = 0; i < charIdx; i++)
footer.append(' ');
footer.append('^');
throw new IOException(message + " in " + source.getName() + ", line " + (idx - 1) + "\n" + footer.toString());
}
public void nextLine() {
line = idx >= lines.size()? null: lines.get(idx);
idx += 1;
charIdx = 0;
}
public String readString() {
skipWhitespace(false);
StringBuilder sb = new StringBuilder();
int start = charIdx;
int end = line.length();
for(; start < end; start++) {
char c = peek();
if(c == ' ' || c == '\n')
break;
skip(1);
sb.append(c);
}
return sb.toString();
}
public int remainingChars() {
return line.length() - charIdx;
}
public String read(int length) throws IOException {
if(length > remainingChars())
error("Tried to read " + length + " chars, only " + remainingChars() + " remaining");
String s = line.substring(charIdx, charIdx+length);
skip(length);
return s;
}
public void skip(int count) {
int skipped = 0;
while(skipped < count) {
int toSkip = Math.min(line.length(), count - skipped);
skipped += toSkip;
charIdx += toSkip;
if(charIdx >= line.length())
nextLine();
}
}
public TextParser expectString(String str) throws IOException {
String read = read(str.length());
if(!read.equalsIgnoreCase(str))
error("Expecting '" + str + "', got '" + read + "'");
return this;
}
public String currentLine() {
return line;
}
public char peek() {
if(charIdx >= line.length())
return '\n';
return line.charAt(charIdx);
}
public char read() {
char c = line.charAt(charIdx);
skip(1);
return c;
}
public String remaining() {
String s = line.substring(charIdx, line.length());
skip(remainingChars());
return s;
}
public boolean isNumber(char c) {
return isCharBetween(c, '0', '9');
}
public boolean isUppercaseAlpha(char c) {
return isCharBetween(c, 'A', 'Z');
}
public boolean isLowercaseAlpha(char c) {
return isCharBetween(c, 'a', 'z');
}
public boolean isWhitespace(char c) {
return c == ' ' || c == '\t' || c == '\n';
}
public boolean isAlpha(char c) {
return isLowercaseAlpha(c) || isUppercaseAlpha(c);
}
public boolean isCharBetween(char c, char start, char end) {
return c >= start && c <= end;
}
public void skipWhitespace(boolean skipNewline) {
char c;
while(isWhitespace(c = peek()) && (skipNewline || c != '\n')) {
if(!skipNewline)
charIdx++;
else
skip(1);
}
}
public void skipWhitespace() {
skipWhitespace(true);
}
public int readHex() throws IOException {
skipWhitespace();
boolean negative = false;
if(peek() == '-') {
negative = true;
skip(1);
}
if(peek() == '+')
skip(1);
expectString("0x");
int val = 0;
final int curLine = idx;
while(idx == curLine && line != null && charIdx < line.length() && (isNumber(peek()) || isCharBetween(peek(), 'A', 'F'))) {
char c = read();
int n = isNumber(c)? c - '0': ((c - 'A') + 10);
val = (val * 16) + n;
}
return negative? -val: val;
}
public int readInt(boolean skipWhitespace, boolean required) throws IOException {
if(skipWhitespace)
skipWhitespace();
boolean negative = false;
final int start = charIdx;
if(peek() == '-') {
negative = true;
skip(1);
}
if(peek() == '+')
skip(1);
int val = 0;
final int curLine = idx;
while(curLine == idx && line != null && charIdx < line.length() && isNumber(peek()))
val = (val * 10) + (read() - '0');
if(required && charIdx - start == 0)
error("Number expected");
return negative? -val : val;
}
public int readInt() throws IOException {
return readInt(true, true);
}
public float readFloat() throws IOException {
int startLine = idx;
skipWhitespace();
boolean negative = peek() == '-';
float result = readInt();
if(peek() == '.') {
skip(1);
float divisor = 10f;
while(startLine == idx && peek() == '0') {
divisor /= 10f;
skip(1);
}
char peek = eof()? '\n': peek();
if(startLine != idx || peek == ' ' || peek == '\t' || peek=='\r' || peek == '\n') // We've changed line or float ended in all 0's
return result;
int floatPart = readInt();
while(floatPart >= divisor)
divisor *= 10f;
float asDecimal = ((float) floatPart) / divisor;
if(result == 0 && negative)
result = -(result + asDecimal);
else
result += asDecimal;
}
return result;
}
public boolean checkString(String needle) {
return line != null && line.toLowerCase().contains(needle.toLowerCase());
}
public Vector3f readVector3() throws IOException {
float x = readFloat();
float y = readFloat();
float z = readFloat();
return new Vector3f(x, y, z);
}
public Angle readAngle() throws IOException {
return new Angle(readFloat());
}
public boolean eof() {
return idx >= lines.size();
}
public String readString(int len) throws IOException {
if(charIdx + len >= line.length())
error("Not enough characters remaining (expected " + len + ", got " + (line.length() - charIdx) + ")");
String r = line.substring(charIdx, charIdx + len);
skip(len);
return r;
}
}