Skip to content

Instantly share code, notes, and snippets.

@jizhilong
Created October 13, 2019 05:51
Show Gist options
  • Save jizhilong/72084b0acf8ac8509c839cefb9294138 to your computer and use it in GitHub Desktop.
Save jizhilong/72084b0acf8ac8509c839cefb9294138 to your computer and use it in GitHub Desktop.
a simple parsec like parer combinator toll in java 8
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
interface CharStream {
/**
* 是否有更多字符等待解析
*/
boolean hasMore();
/**
* 读取第一个字符(如果有的话)
*/
char peek();
/**
* 在当前字符流中前进一步
*/
CharStream advance();
}
class CharStreamImpl implements CharStream {
private final char[] chars;
private int pos;
public CharStreamImpl(String string) {
this(string.toCharArray(), 0);
}
public CharStreamImpl(char[] chars, int pos) {
this.chars = chars;
this.pos = pos;
}
public boolean hasMore() {
return pos < chars.length;
}
public char peek() {
return chars[pos];
}
public CharStream advance() {
return new CharStreamImpl(chars, pos + 1);
}
}
class Digits {
static final Digits empty = new Digits(Collections.emptyList());
final List<Character> digits;
Digits(List<Character> digits) {
this.digits = digits;
}
@Override
public String toString() {
return digits.stream().map(String::valueOf).collect(Collectors.joining());
}
public boolean isEmpty() {
return digits.isEmpty();
}
}
class Int {
static final Int empty = unsigned(Digits.empty);
final Character flag;
final Digits digits;
public Int(Character flag, Digits digits) {
this.flag = flag;
this.digits = digits;
}
static Int unsigned(Digits digits) {
return new Int(null, digits);
}
static Int signed(Character flag, Int unsigned) {
return new Int(flag, unsigned.digits);
}
@Override
public String toString() {
return flag == null ? digits.toString() : flag + digits.toString();
}
public boolean isEmpty() {
return digits.isEmpty();
}
}
class Float {
final Character flag;
final Digits intPart;
final Digits fractionPart;
public Float(Character flag, Digits intPart, Digits fractionPart) {
this.flag = flag;
this.intPart = intPart;
this.fractionPart = fractionPart;
}
static Float full(Digits intPart, Digits fractionPart) {
assert !intPart.isEmpty();
assert !fractionPart.isEmpty();
return new Float(null, intPart, fractionPart);
}
static Float onlyIntPart(Digits intPart) {
assert !intPart.isEmpty();
return new Float(null, intPart, Digits.empty);
}
static Float onlyFractionPart(Digits fractionPart) {
return new Float(null, Digits.empty, fractionPart);
}
static Float signed(Character flag, Float unsigned) {
return new Float(flag, unsigned.intPart, unsigned.fractionPart);
}
@Override
public String toString() {
String formattedUnsignedPart = String.format("%s.%s", intPart, fractionPart);
return flag == null ? formattedUnsignedPart : flag + formattedUnsignedPart;
}
public boolean isEmpty() {
return intPart.isEmpty() && fractionPart.isEmpty();
}
}
class Number {
final Float floatPart;
final Int expPart;
Number(Float floatPart, Int expPart) {
this.floatPart = floatPart;
this.expPart = expPart;
}
static Number full(Float floatPart, Int expPart) {
assert !floatPart.isEmpty();
assert !expPart.isEmpty();
return new Number(floatPart, expPart);
}
static Number justFloat(Float floatPart) {
assert !floatPart.isEmpty();
return new Number(floatPart, Int.empty);
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(floatPart);
if (expPart != null) {
stringBuilder.append('e').append(expPart);
}
return stringBuilder.toString();
}
}
class ParseResult<T> {
private final Throwable error;
private final T value;
private final CharStream stream;
ParseResult(Throwable error, T value, CharStream stream) {
this.error = error;
this.value = value;
this.stream = stream;
}
public static <T> ParseResult<T> success(T value, CharStream stream) {
Objects.requireNonNull(stream);
return new ParseResult<>(null, value, stream);
}
public static <T> ParseResult<T> error(Throwable error, CharStream stream) {
Objects.requireNonNull(stream);
return new ParseResult<>(error, null, stream);
}
public boolean isSucceed() {
return value != null;
}
public boolean isFailed() {
return error != null;
}
public Throwable getError() {
return error;
}
public T getValue() {
return value;
}
public CharStream getStream() {
return stream;
}
public <T1> ParseResult<T1> valueMap(Function<T, T1> mapper) {
if (isFailed()) {
@SuppressWarnings("unchecked")
ParseResult<T1> valueMappedParseResult = (ParseResult<T1>) this;
return valueMappedParseResult;
} else {
return success(mapper.apply(value), stream);
}
}
public ParseResult<T> onError(Consumer<Throwable> consumer) {
if (isFailed()) {
consumer.accept(error);
}
return this;
}
public ParseResult<T> onValue(Consumer<T> consumer) {
if (isSucceed()) {
consumer.accept(value);
}
return this;
}
}
interface Parser<T> {
ParseResult<T> parse(CharStream stream);
default ParseResult<T> parse(String string) {
return parse(new CharStreamImpl(string));
}
default <T1> Parser<T1> map(Function<T, T1> mapper) {
return stream -> parse(stream).valueMap(mapper);
}
default <T1> Parser<T1> flatMap(Function<T, Parser<T1>> mapper) {
return stream -> {
ParseResult<T> result = parse(stream);
if (result.isFailed()) {
return (ParseResult<T1>) result;
} else {
Parser<T1> nextParser = mapper.apply(result.getValue());
return nextParser.parse(result.getStream());
}
};
}
default Parser<T> or(Parser<T> recoverParser) {
return stream -> {
ParseResult<T> result = parse(stream);
if (result.isFailed()) {
return recoverParser.parse(stream);
} else {
return result;
}
};
}
default Parser<T> filter(Predicate<T> predicate) {
return stream -> {
ParseResult<T> result = parse(stream);
if (result.isFailed()) {
return ParseResult.error(result.getError(), stream);
} else {
T value = result.getValue();
if (predicate.test(value)) {
return result;
} else {
return ParseResult.error(
new Throwable(String.format("%s not match against predicate", value)), stream);
}
}
};
}
default <T1> Parser<T1> then(Parser<T1> parser) {
return stream -> {
ParseResult<T> result = parse(stream);
if (result.isFailed()) {
return (ParseResult<T1>) result;
} else {
return parser.parse(result.getStream());
}
};
}
default Parser<T> beginWith(Parser<?> headParser) {
return headParser.then(this);
}
default Parser<T> surroundWith(Parser<?> headParser, Parser<?> tailParser) {
return this.beginWith(headParser).endWith(tailParser);
}
default Parser<T> surroundWith(Parser<?> surroundingParser) {
return surroundWith(surroundingParser, surroundingParser);
}
default Parser<T> endWith(Parser<?> tailParser) {
return this.flatMap(value -> tailParser.then(Parsers.eof).then(Parsers.just(value)));
}
}
public class Parsers {
public static <T> Parser<T> just(T value) {
return stream -> ParseResult.success(value, stream);
}
public static <T1, T2, T3> Parser<T3> seq(Parser<T1> parser1, Parser<T2> parser2,
BiFunction<T1, T2, T3> merger) {
return parser1.flatMap(t1 -> parser2.map(t2 -> merger.apply(t1, t2)));
}
public static <T> Parser<List<T>> more(Parser<T> subParser) {
return stream -> {
List<T> results = new ArrayList<>();
ParseResult<T> result = subParser.parse(stream);
while (result.isSucceed()) {
results.add(result.getValue());
stream = result.getStream();
result = subParser.parse(stream);
}
return ParseResult.success(results, stream);
};
}
public static <T> Parser<List<T>> more1(Parser<T> subParser) {
return more(subParser).filter(list -> !list.isEmpty());
}
public static Predicate<Character> eq(char ch) {
return character -> character == ch;
}
public static final Parser<Character> one = stream -> {
if (!stream.hasMore()) {
return ParseResult.error(new Throwable("no more characters"), stream);
} else {
return ParseResult.success(stream.peek(), stream.advance());
}
};
public static Parser<Character> digit = one.filter(ch -> ch >= '0' && ch <= '9');
public static Parser<Character> space = one.filter(eq(' '));
public static Parser<List<Character>> spaces = more(space);
public static Parser<Void> eof = stream -> {
if (stream.hasMore()) {
return ParseResult.error(new Throwable("there are more characters"), stream);
} else {
return ParseResult.success(null, stream);
}
};
public static final Parser<Character> positiveFlag = one.filter(eq('+'));
public static final Parser<Character> negativeFlag = one.filter(eq('-'));
public static final Parser<Character> flag = positiveFlag.or(negativeFlag).or(just(null));
public static final Parser<Character> point = one.filter(eq('.'));
public static final Parser<Character> expFlag = one.filter(ch -> ch == 'e' || ch == 'E');
public static final Parser<Digits> digits0plus = more(digit).map(Digits::new);
public static final Parser<Digits> digits1plus = more1(digit).map(Digits::new);
// 无符号整数
public static final Parser<Int> unsignedInt = digits1plus.map(Int::unsigned);
// 有符号整数
public static final Parser<Int> signedInt = seq(flag, unsignedInt, Int::signed);
// 包含整数部分的无符号浮点数
public static final Parser<Float> fullUnsignedFloat =
seq(digits1plus, point.then(digits0plus), Float::full);
// 不含整数部分的无符号浮点数
public static final Parser<Float> onlyFractionPartUnsignedFloat =
point.then(digits1plus).map(Float::onlyFractionPart);
// 只含整数部分的无符号浮点数
public static final Parser<Float> onlyIntPartUnsignedFloat =
digits1plus.map(Float::onlyIntPart);
// 任意无符号浮点数
public static final Parser<Float> unsignedFloat =
fullUnsignedFloat.or(onlyFractionPartUnsignedFloat).or(onlyIntPartUnsignedFloat);
// 任意有符号浮点数
public static final Parser<Float> floatParser = seq(flag, unsignedFloat, Float::signed);
// 包含指数部分的科学计数
public static final Parser<Number> withExpNumber = seq(floatParser, expFlag.then(signedInt),
Number::full);
// 不包含指数部分的科学计数
public static final Parser<Number> justFloatNumber = floatParser.map(Number::justFloat);
// 任意科学计数
public static final Parser<Number> number = withExpNumber.or(justFloatNumber);
// 前后为空白的任意科学计数, 用于解决leetcode面试题
public static final Parser<Number> numberSurroundWithSpaces = number.surroundWith(spaces);
public static void main(String[] args) {
numberSurroundWithSpaces
.parse(" -.0123E-123")
.onValue(System.out::println)
.onError(Throwable::printStackTrace);
floatParser
.surroundWith(spaces)
.parse(" 0.78921124 ")
.onValue(System.out::println)
.onError(Throwable::printStackTrace);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment