package openmods.calc.parsing;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import java.util.Iterator;
import org.apache.commons.lang3.tuple.Pair;
public abstract class PositionalNotationParser<I, F> {
private static final Splitter DOT_SPLITTER = Splitter.on('.');
public interface Accumulator<E> {
public void add(int digit);
public E get();
}
protected abstract Accumulator<I> createIntegerAccumulator(int radix);
protected abstract Accumulator<F> createFractionalAccumulator(int radix);
private interface PartParser {
public <E> E parsePart(Accumulator<E> accumulator, String input, int radix);
}
private static final PartParser SIMPLE_DIGIT_PART_PARSER = new PartParser() {
@Override
public <E> E parsePart(Accumulator<E> accumulator, String input, int radix) {
final char[] charArray = input.toCharArray();
for (char ch : charArray) {
if (ch == '_') continue;
final int digit = Character.digit(ch, radix);
if (digit < 0) throw invalidDigit(input, radix, ch);
accumulator.add(digit);
}
return accumulator.get();
}
};
private static final PartParser QUOTED_DIGIT_PART_PARSER = new PartParser() {
@Override
public <E> E parsePart(Accumulator<E> accumulator, String input, int radix) {
String reminder = input;
final int digitRange = Math.min(Character.MAX_RADIX, radix);
while (!reminder.isEmpty()) {
final char ch = reminder.charAt(0);
reminder = reminder.substring(1);
final int digit;
if (ch == '_') continue;
if (ch == '\'') {
final int nextQuote = reminder.indexOf('\'');
if (nextQuote < 0) throw new NumberFormatException("Unmatched quote in " + input);
final String digitStr = reminder.substring(0, nextQuote);
digit = Integer.parseInt(digitStr);
if (digit >= radix) throw invalidDigit(input, radix, digitStr);
reminder = reminder.substring(nextQuote + 1);
} else {
digit = Character.digit(ch, digitRange);
if (digit < 0) throw invalidDigit(input, radix, ch);
}
accumulator.add(digit);
}
return accumulator.get();
}
};
private Pair<I, F> parseNumber(PartParser partParser, String value, int radix) {
final Iterator<String> parts = DOT_SPLITTER.split(value).iterator();
Preconditions.checkState(parts.hasNext(), "Invalid input '%s'", value);
final String integerPart = parts.next();
final Accumulator<I> integerAccumulator = createIntegerAccumulator(radix);
final I integer = partParser.parsePart(integerAccumulator, integerPart, radix);
if (!parts.hasNext()) return Pair.of(integer, null);
final String fractionalPart = parts.next();
final Accumulator<F> fractionalAccumulator = createFractionalAccumulator(radix);
final F fraction = partParser.parsePart(fractionalAccumulator, fractionalPart, radix);
Preconditions.checkState(!parts.hasNext(), "More than one comman in '%s'", value);
return Pair.of(integer, fraction);
}
private Pair<I, F> parseSimpleNumber(String value, int radix) {
return parseNumber(SIMPLE_DIGIT_PART_PARSER, value, radix);
}
private Pair<I, F> parseQuotedNumber(String value, int radix) {
return parseNumber(QUOTED_DIGIT_PART_PARSER, value.replace("\"", "''"), radix);
}
private Pair<I, F> parseQuotedNumber(String value) {
final int radixEnd = value.indexOf('#');
final String radixStr = value.substring(0, radixEnd);
if (radixStr.isEmpty()) throw new NumberFormatException("No radix given");
final int radix = Integer.parseInt(radixStr);
if (radix < Character.MIN_RADIX) throw new NumberFormatException("Base must be larger than " + Character.MIN_RADIX);
final String numberStr = value.substring(radixEnd + 1);
return parseQuotedNumber(numberStr, radix);
}
public Pair<I, F> parseString(String value, int radix) {
if (radix < Character.MIN_RADIX) throw new NumberFormatException("Base must be larger than " + Character.MIN_RADIX);
return parseQuotedNumber(value, radix);
}
public Pair<I, F> parseToken(Token token) {
switch (token.type) {
case BIN_NUMBER:
return parseSimpleNumber(token.value, 2);
case OCT_NUMBER:
return parseSimpleNumber(token.value, 8);
case DEC_NUMBER:
return parseSimpleNumber(token.value, 10);
case HEX_NUMBER:
return parseSimpleNumber(token.value, 16);
case QUOTED_NUMBER:
return parseQuotedNumber(token.value);
default:
throw new InvalidTokenException(token);
}
}
private static NumberFormatException invalidDigit(String value, int radix, Object ch) {
return new NumberFormatException(String.format("Number '%s' in base %d contains invalid digit '%s'", value, radix, ch));
}
}