Skip to content

Commit

Permalink
Update README.md and change some combinator names
Browse files Browse the repository at this point in the history
  • Loading branch information
BjoernLoetters committed Jan 12, 2025
1 parent 015f110 commit 76e0186
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 26 deletions.
47 changes: 40 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,37 @@

# Java Parser Combinators

The Java Parser Combinators library provides parser combinators as they are known from functional languages like Scala or Haskell.
**Java Parser Combinators** is a lightweight library designed for the rapid prototyping of parsers in Java.
It aims to balance ease of use and functionality, deliberately placing less emphasis on performance.

More information will follow in the near future.
### Parser Combinators

### Example
In general, parser combinators are a powerful technique for constructing complex parsers by composing smaller, reusable parsers.
Each parser is responsible for recognizing a specific part of the input, and combinators are functions that combine these basic parsers into more complex ones (hence the name).
This approach allows the rapid development of parsers with all the benefits of ordinary program code.
So, instead of generating a parser using a third party tool and a grammar specification, we "program" the syntax of our language in our primary programming language.

In Java Parser Combinators, parsers are implemented as functions:
They take an `Input` (which is basically a `String`) and produce a `ParseResult<T>`.
Here, the type `T` indicates the type of the result.
So, for example:
- A character parser takes an `Input` and produces a `ParseResult<Character>`
- A string parser takes an `Input` and produces a `ParseResult<String>`
- A parser which concatenates a character with a string parser produces a `ParseResult<Tuple<Character, String>>`

Of course, a parse may also fail.
For this reason, a `ParseResult<T>` may either be a `Success<T>` (containing the desired result) or a `Failure<T>` (containing an error message).

### Getting Started

##### Installation

At the moment, Java Parser Combinators are only available as a `jar`-release via the [release page](https://github.com/BjoernLoetters/Java-Parser-Combinators/releases).
In the near future, they will also be available as a maven package.
So, to get started just download the latest `jar`-release and add it to the classpath of your project.
In IntelliJ this can be done by right-clicking on the `jar`-file and selecting `Add as library ...`.

##### Usage Example

```java
import static jcombinators.common.StringParser.*;
Expand All @@ -18,11 +44,11 @@ public class MyParser {
public static Parser<Integer> number = regex("[+-]?[0-9]+").map(Integer::parseInt);

// A parser which parses additions.
public static Parser<Integer> add = number.keepLeft(character('+')).and(number)
public static Parser<Integer> add = number.andl(character('+')).and(number)
.map(tuple -> tuple.first() + tuple.second());

public static void main(final String[] arguments) {
// Create an input with the name 'My Test Input'.
// Create an input with the name 'My Test Input' (for error reporting).
Input input = Input.of("My Test Input", "42+0");

// Use the 'add' parser to parse the above input. Note how a parser is
Expand All @@ -42,5 +68,12 @@ public class MyParser {
}
```

This example outputs `Success: 42`. It makes use of the `keepLeft` and `and` combinators to concatenate a number with the `+` sign and another number.
While `keepLeft` and `and` behave very similar, `keepLeft` only returns the result of the first parser and drops the result of the second one upon success.
### Contributing

Contributions of any kind are welcome!
Whether it’s reporting issues, suggesting features, or submitting pull requests, your help is appreciated.
If you find this library useful, consider sharing it with others.

### License

This project is licensed under the [MIT](LICENSE) license.
19 changes: 9 additions & 10 deletions src/main/java/jcombinators/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@FunctionalInterface
public interface Parser<T> {
public interface Parser<T> extends Function<Input, Result<T>> {

public abstract Result<T> apply(final Input input);

Expand All @@ -43,14 +42,18 @@ public default <U> Parser<U> flatMap(final Function<T, Parser<U>> function) {
return new FlatMapParser<>(this, function);
}

public default <U> Parser<U> keepRight(final Parser<U> parser) {
public default <U> Parser<U> andr(final Parser<U> parser) {
return this.flatMap(ignore -> parser);
}

public default <U> Parser<T> keepLeft(final Parser<U> parser) {
public default <U> Parser<T> andl(final Parser<U> parser) {
return this.flatMap(result -> parser.map(ignore -> result));
}

public default <U> Parser<Tuple<T, U>> and(final Parser<U> parser) {
return this.flatMap(first -> parser.map(second -> new Tuple<>(first, second)));
}

public default Parser<T> commit() {
return (input -> switch (apply(input)) {
case Success<T> success -> success;
Expand All @@ -59,10 +62,6 @@ public default Parser<T> commit() {
});
}

public default <U> Parser<Tuple<T, U>> and(final Parser<U> parser) {
return this.flatMap(first -> parser.map(second -> new Tuple<>(first, second)));
}

public default Parser<Void> not() {
return (input -> switch (apply(input)) {
case Success<T> success -> new Error<>(Failure.format(input, new Negation(this.description())), input);
Expand Down Expand Up @@ -95,7 +94,7 @@ public default Parser<T> or(final Parser<T> alternative) {
return or(this, alternative);
}

public static <T> Parser<T> chainLeft(final Parser<T> element, final Parser<BiFunction<T, T, T>> separator) {
public static <T> Parser<T> chainl(final Parser<T> element, final Parser<BiFunction<T, T, T>> separator) {
return element.and(separator.and(element).repeat()).map(tuple -> {
T result = tuple.first();

Expand All @@ -107,7 +106,7 @@ public static <T> Parser<T> chainLeft(final Parser<T> element, final Parser<BiFu
});
}

public static <T> Parser<T> chainRight(final Parser<T> element, final Parser<BiFunction<T, T, T>> separator) {
public static <T> Parser<T> chainr(final Parser<T> element, final Parser<BiFunction<T, T, T>> separator) {
return element.and(separator.and(element).repeat()).map(tuple -> {
if (tuple.second().isEmpty()) {
return tuple.first();
Expand Down
12 changes: 6 additions & 6 deletions src/test/java/jcombinators/CombinatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public void repeat1EmptyInputTest() {
public void keepRightSuccessTest() {
Parser<Character> first = character('a');
Parser<Character> second = character('b');
Parser<Character> parser = first.keepRight(second);
Parser<Character> parser = first.andr(second);

assertSuccess(parser, 'b', "ab");
}
Expand All @@ -55,7 +55,7 @@ public void keepRightSuccessTest() {
public void keepRightFailureTest() {
Parser<Character> first = character('a');
Parser<Character> second = character('b');
Parser<Character> parser = first.keepRight(second);
Parser<Character> parser = first.andr(second);

assertFailure(parser, "unexpected character 'c', expected the literal 'b'", "ac");
}
Expand All @@ -64,7 +64,7 @@ public void keepRightFailureTest() {
public void keepLeftSuccessTest() {
Parser<Character> first = character('a');
Parser<Character> second = character('b');
Parser<Character> parser = first.keepLeft(second);
Parser<Character> parser = first.andl(second);

assertSuccess(parser, 'a', "ab");
}
Expand All @@ -73,7 +73,7 @@ public void keepLeftSuccessTest() {
public void keepLeftFailureTest() {
Parser<Character> first = character('a');
Parser<Character> second = character('b');
Parser<Character> parser = first.keepLeft(second);
Parser<Character> parser = first.andl(second);

assertFailure(parser, "unexpected character 'c', expected the literal 'b'", "ac");
}
Expand Down Expand Up @@ -162,7 +162,7 @@ public void separate1FailureTest() {
public void chainLeftSuccessTest() {
final Parser<Integer> number = regex("[0-9]").map(Integer::parseInt);
final Parser<BiFunction<Integer, Integer, Integer>> plus = character('+').map(op -> Integer::sum);
final Parser<Integer> parser = Parser.chainLeft(number, plus);
final Parser<Integer> parser = Parser.chainl(number, plus);

assertSuccess(parser, 6, "1+2+3");
}
Expand All @@ -171,7 +171,7 @@ public void chainLeftSuccessTest() {
public void chainRightSuccessTest() {
final Parser<Integer> number = regex("[0-9]").map(Integer::parseInt);
final Parser<BiFunction<Integer, Integer, Integer>> exponent = character('^').map(op -> (a, b) -> (int) Math.pow(a, b));
final Parser<Integer> parser = Parser.chainRight(number, exponent);
final Parser<Integer> parser = Parser.chainr(number, exponent);

assertSuccess(parser, 2, "2^3^0");
}
Expand Down
3 changes: 0 additions & 3 deletions src/test/java/jcombinators/ParserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,9 @@
import jcombinators.input.Input;
import jcombinators.result.Failure;
import jcombinators.result.Success;
import jdk.jfr.StackTrace;
import org.junit.Test;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import static org.junit.Assert.assertEquals;
Expand Down

0 comments on commit 76e0186

Please sign in to comment.