Created
October 13, 2019 05:51
-
-
Save jizhilong/72084b0acf8ac8509c839cefb9294138 to your computer and use it in GitHub Desktop.
a simple parsec like parer combinator toll in java 8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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