diff --git a/readme.md b/readme.md index 53ba4f1..da77cb7 100644 --- a/readme.md +++ b/readme.md @@ -12,32 +12,52 @@ Just like the usage of Parser Combinator in Haskell, jparser does not generate A ``` -## hello world +## hello world: Simple Calculator ```java +public class Calculator { @Test - public void testHelloWorld() { - String time = "2023-05-01 12:59:59"; - Parser timeParser = NumberParsers.anyIntStr() //year - .chain(() -> TextParsers.one('-').ignore()) //- - .chain(() -> NumberParsers.anyIntStr()) //mon - .chain(() -> TextParsers.one('-').ignore()) //- - .chain(() -> NumberParsers.anyIntStr()) //day - .chain(() -> TextParsers.one(' ').ignore())// ' ' - .chain(() -> NumberParsers.anyIntStr()) //hour - .chain(() -> TextParsers.one(':').ignore()) // ':' - .chain(() -> NumberParsers.anyIntStr()) //minute - .chain(() -> TextParsers.one(':').ignore()) // ':' - .chain(() -> NumberParsers.anyIntStr()) //second - .chain(() -> TextParsers.eof()); - Result result = timeParser.runParser(Buffer.builder().data(time.getBytes()).build()); - assert result.get(0) == 2023; - assert result.get(1) == 5; - assert result.get(2) == 1; - assert result.get(3) == 12; - assert result.get(4) == 59; - assert result.get(5) == 59; + public void testCalc() { + Result result = expr().parse(Buffer.builder().data("(1+2)*3-(4*2)".getBytes()).build()); + assert result.get(0).compareTo(1.0) == 0; + result = expr().parse(Buffer.builder().data("1+2*3-(4*2)".getBytes()).build()); + assert result.get(0).compareTo(-1.0) == 0; } + + public Parser expr() { + return Parser.choose( + () -> term().chain(TextParsers.one('+').ignore()) + .chain(() -> expr()).map(s -> (double)s.get(0) + (double)s.get(1)), + () -> term().chain(TextParsers.one('-').ignore()) + .chain(() -> expr()).map(s -> (double)s.get(0) - (double)s.get(1)), + () -> term() + ); + } + + public Parser term() { + return Parser.choose( + () -> factor().chain(TextParsers.one('*').trim(false).ignore()) + .chain(() -> term()).map(s -> (double)s.get(0) * (double)s.get(1)), + () -> factor().chain(TextParsers.one('/').trim(false).ignore()) + .chain(() -> term()).map(s -> (double)s.get(0) / (double)s.get(1)), + () -> factor() + ); + } + + public Parser factor() { + return Parser.choose( + TextParsers.one('(').ignore() + .chain(() -> expr()) + .chain(TextParsers.one(')').ignore()), + number() + ); + } + + public Parser number() { + return NumberParsers.anyDoubleStr(); + } +} + ``` ## basic parsers diff --git a/src/main/java/io/github/janlely/jparser/Parser.java b/src/main/java/io/github/janlely/jparser/Parser.java index 51bfc39..2a60d0e 100644 --- a/src/main/java/io/github/janlely/jparser/Parser.java +++ b/src/main/java/io/github/janlely/jparser/Parser.java @@ -481,6 +481,31 @@ public Result parse(IBuffer buffer) { }; } + /** + * Same as Combinator Choose + * @param parser A Parser generator + * @return A new Parser that is composed of the specific Parser generator + */ + public Parser or(Parser parser) { + return new Parser() { + @Override + public Result parse(IBuffer buffer) { + Result result = Parser.this.runParser(buffer); + if (result.isSuccess()) { + return result; + } + Result result2 = parser.runParser(buffer); + if (result2.isSuccess()) { + return result2; + } + return Result.builder() + .pos(buffer.getPos()) + .errorMsg("No suitable Parser to choose") + .build(); + } + }; + } + /** * make this Parser optional * @return A new Parser @@ -594,6 +619,19 @@ public static Parser choose(Supplier ...parsers) { return parser; } + /** + * choose a Parser from array of Parser + * @param parsers Parsers candidates + * @return A new Parser that is composed of the parsers + */ + public static Parser choose(Parser ...parsers) { + Parser parser = Parser.broken(); + for (Parser p : parsers) { + parser = parser.or(p); + } + return parser; + } + /** * choose a Parser from array of Parser * @param parsers Parsers candidates diff --git a/src/main/java/io/github/janlely/jparser/parsers/NumberParsers.java b/src/main/java/io/github/janlely/jparser/parsers/NumberParsers.java index 38de65b..1be8af9 100644 --- a/src/main/java/io/github/janlely/jparser/parsers/NumberParsers.java +++ b/src/main/java/io/github/janlely/jparser/parsers/NumberParsers.java @@ -35,6 +35,17 @@ public static Parser anyIntStr() { .map(s -> Integer.parseInt((String) s.get(0))); } + /** + * Parse any double encoded as a string. + * @return A new Parser + */ + public static Parser anyDoubleStr() { + return TextParsers.one('-').optional().chain(NumberParsers.anyIntStr()) + .chain(TextParsers.one('.').chain(NumberParsers::anyDoubleStr).optional()) + .map(Mapper.toStr()) + .map(s -> Double.parseDouble((String) s.get(0))); + } + /** * Parse an arbitrary long integer encoded in big-endian format. * @return A new Parser diff --git a/src/test/java/io/github/janlely/jparser/Calculator.java b/src/test/java/io/github/janlely/jparser/Calculator.java new file mode 100644 index 0000000..d6bab0c --- /dev/null +++ b/src/test/java/io/github/janlely/jparser/Calculator.java @@ -0,0 +1,51 @@ +package io.github.janlely.jparser; + +import io.github.janlely.jparser.parsers.NumberParsers; +import io.github.janlely.jparser.parsers.TextParsers; +import io.github.janlely.jparser.util.Buffer; +import org.junit.Test; + +public class Calculator { + + @Test + public void testCalc() { + Result result = expr().parse(Buffer.builder().data("(1+2)*3-(4*2)".getBytes()).build()); + assert result.get(0).compareTo(1.0) == 0; + result = expr().parse(Buffer.builder().data("1+2*3-(4*2)".getBytes()).build()); + assert result.get(0).compareTo(-1.0) == 0; + } + + public Parser expr() { + return Parser.choose( + () -> term().chain(TextParsers.one('+').ignore()) + .chain(() -> expr()).map(s -> (double)s.get(0) + (double)s.get(1)), + () -> term().chain(TextParsers.one('-').ignore()) + .chain(() -> expr()).map(s -> (double)s.get(0) - (double)s.get(1)), + () -> term() + ); + } + + public Parser term() { + return Parser.choose( + () -> factor().chain(TextParsers.one('*').trim(false).ignore()) + .chain(() -> term()).map(s -> (double)s.get(0) * (double)s.get(1)), + () -> factor().chain(TextParsers.one('/').trim(false).ignore()) + .chain(() -> term()).map(s -> (double)s.get(0) / (double)s.get(1)), + () -> factor() + ); + } + + public Parser factor() { + return Parser.choose( + TextParsers.one('(').ignore() + .chain(() -> expr()) + .chain(TextParsers.one(')').ignore()), + number() + ); + } + + public Parser number() { + return NumberParsers.anyDoubleStr(); + } +} +