Skip to content

Commit

Permalink
Add more JavaDoc documentation and refactor the Input class, such tha…
Browse files Browse the repository at this point in the history
…t the Position class now depends on the respective Input
  • Loading branch information
BjoernLoetters committed Jan 30, 2025
1 parent 2461fe4 commit 278e799
Show file tree
Hide file tree
Showing 17 changed files with 562 additions and 328 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ jobs:
- name: Build JAR-File
run: mvn clean package -DskipTests

- name: Generate JavaDoc
run: mvn javadoc:jar

- name: Upload Release
uses: marvinpinto/action-automatic-releases@latest
with:
Expand All @@ -38,4 +41,5 @@ jobs:
title: "Release ${{ github.ref_name }}"
files: |
LICENSE
core/target/*-javadoc.jar
core/target/*.jar
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public class MyParser extends StringParsing {

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

public static void main(final String[] arguments) {
// Create an input of characters with the name 'My Test Input' (for error reporting).
Expand Down
13 changes: 13 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.11.2</version>
<executions>
<execution>
<id>attach-javadoc</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

Expand Down
54 changes: 26 additions & 28 deletions core/src/main/java/jcombinators/Parsing.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package jcombinators;

import jcombinators.data.Tuple;
import jcombinators.data.Product;
import jcombinators.description.Choice;
import jcombinators.description.Description;
import jcombinators.description.Empty;
import jcombinators.input.Input;
import jcombinators.position.Position;

import java.util.*;
import java.util.function.BiFunction;
Expand Down Expand Up @@ -248,9 +247,9 @@ public <U> Result<U> flatMap(final Function<T, Result<U>> function) {
public static String format(final Input<?> input, final Description description) {
final Optional<String> expected = description.normalize().describe();
if (expected.isEmpty()) {
return String.format("syntax error in %s: unexpected %s", input.position(), input.describe());
return String.format("syntax error in %s: unexpected %s", input.position(), input.position().describe());
} else {
return String.format("syntax error in %s: unexpected %s, expected %s", input.position(), input.describe(), expected.get());
return String.format("syntax error in %s: unexpected %s, expected %s", input.position(), input.position().describe(), expected.get());
}
}

Expand Down Expand Up @@ -362,8 +361,8 @@ public final <U> Parser<U> flatMap(final Function<T, Parser<U>> function) {
return new FlatMapParser<T, U>(this, function);
}

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

public final <U> Parser<T> keepLeft(final Parser<U> right) {
Expand All @@ -387,11 +386,11 @@ public final Parser<List<T>> repeat() {
}

public final Parser<List<T>> repeat1() {
return this.and(repeat()).map(tuple -> tuple.map(Stream::of, List::stream).fold(Stream::concat).toList());
return this.and(repeat()).map(product -> product.map(Stream::of, List::stream).fold(Stream::concat).toList());
}

public final Parser<List<T>> separate1(final Parser<?> separator) {
return this.and(separator.and(this).repeat()).map(tuple -> tuple.map(Stream::of, list -> list.stream().map(Tuple::second)).fold(Stream::concat).toList());
return this.and(separator.and(this).repeat()).map(product -> product.map(Stream::of, list -> list.stream().map(Product::second)).fold(Stream::concat).toList());
}

public final Parser<List<T>> separate(final Parser<?> separator) {
Expand Down Expand Up @@ -422,11 +421,10 @@ public final <T> Result<T> parse(final Parser<T> parser, final Input<I> input) {
final Result<T> result = parser.apply(input);

if (result.isSuccess()) {
final Input<I> rest = result.rest.skip();
if (rest.isEmpty()) {
return new Success<>(result.getOrFail(), rest);
if (result.rest.isEmpty()) {
return new Success<>(result.getOrFail(), result.rest);
} else {
return new Error<>(Failure.format(rest, new Empty()), rest);
return new Error<>(Failure.format(result.rest, new Empty()), result.rest);
}
} else {
return result;
Expand Down Expand Up @@ -494,10 +492,10 @@ public final <T> Parser<List<T>> sequence(final Parser<? extends T>... elements)
}

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

for (Tuple<BiFunction<T, T, T>, T> next : tuple.second()) {
for (Product<BiFunction<T, T, T>, T> next : product.second()) {
result = next.first().apply(result, next.second());
}

Expand All @@ -510,24 +508,24 @@ public <T> Parser<T> chainLeft(final Parser<T> element, final Parser<BiFunction<
}

public <T> Parser<T> chainRight1(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();
return element.and(separator.and(element).repeat()).map(product -> {
if (product.second().isEmpty()) {
return product.first();
} else {
final List<Tuple<BiFunction<T, T, T>, T>> reversed = tuple.second().reversed();
final Iterator<Tuple<BiFunction<T, T, T>, T>> iterator = reversed.iterator();
final List<Product<BiFunction<T, T, T>, T>> reversed = product.second().reversed();
final Iterator<Product<BiFunction<T, T, T>, T>> iterator = reversed.iterator();

final Tuple<BiFunction<T, T, T>, T> first = iterator.next();
final Product<BiFunction<T, T, T>, T> first = iterator.next();
T result = first.second();
BiFunction<T, T, T> combiner = first.first();

while (iterator.hasNext()) {
final Tuple<BiFunction<T, T, T>, T> next = iterator.next();
final Product<BiFunction<T, T, T>, T> next = iterator.next();
result = combiner.apply(next.second(), result);
combiner = next.first();
}

result = combiner.apply(tuple.first(), result);
result = combiner.apply(product.first(), result);
return result;
}
});
Expand All @@ -537,7 +535,7 @@ public <T> Parser<T> chainRight(final Parser<T> element, final Parser<BiFunction
return chainRight1(element, separator).optional().map(result -> result.orElse(otherwise));
}

public final <T> Parser<T> position(final Parser<Function<Position, T>> parser) {
public final <T> Parser<T> position(final Parser<Function<Input<I>.Position, T>> parser) {
return new PositionParser<>(parser);
}

Expand Down Expand Up @@ -697,9 +695,9 @@ public Result<List<T>> apply(final Input<I> input) {

private final class PositionParser<T> extends Parser<T> {

private final Parser<Function<Position, T>> parser;
private final Parser<Function<Input<I>.Position, T>> parser;

private PositionParser(final Parser<Function<Position, T>> parser) {
private PositionParser(final Parser<Function<Input<I>.Position, T>> parser) {
this.parser = parser;
}

Expand All @@ -710,7 +708,7 @@ public Description description() {

@Override
public Result<T> apply(final Input<I> input) {
final Position position = input.position();
final Input<I>.Position position = input.position();
return parser.apply(input).map(function -> function.apply(position));
}

Expand Down Expand Up @@ -776,7 +774,7 @@ public LogParser(final String name, final Parser<T> parser) {

@Override
public Result<T> apply(final Input<I> input) {
System.out.printf("trying '%s' in %s (%s)\n", name, input.position(), input.describe());
System.out.printf("trying '%s' in %s (%s)\n", name, input.position(), input.position().describe());
final Result<T> result = parser.apply(input);
return switch (result) {
case Success<T> success -> {
Expand Down
49 changes: 13 additions & 36 deletions core/src/main/java/jcombinators/StringParsing.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@
import jcombinators.description.Description;
import jcombinators.description.Literal;
import jcombinators.description.RegExp;
import jcombinators.input.CharInput;
import jcombinators.input.CharacterInput;
import jcombinators.input.Input;

import java.io.StringReader;
import java.math.BigInteger;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand Down Expand Up @@ -65,39 +63,20 @@ public Description description() {

@Override
public Result<String> apply(Input<Character> input) {
input = input.skip();

Input<Character> current = input;
int i = 0;

while (!current.isEmpty() && i < literal.length()) {
final int a;
final int b = Character.codePointAt(literal, i);

final Input<Character> previous = current;
final char high = current.head();
if (Character.isHighSurrogate(high)) {
final Input<Character> temporary = current.tail();
if (!temporary.isEmpty() && Character.isLowSurrogate(temporary.head())) {
current = temporary;
a = Character.toCodePoint(high, temporary.head());
} else {
a = high;
}
} else {
a = high;
}
int index = 0;

if (a != b) {
return new Error<String>(Failure.format(previous, description()), input);
while (current.nonEmpty() && index < literal.length()) {
if (current.head() != literal.charAt(index)) {
return new Error<String>(Failure.format(current, description()), input);
}

++index;
current = current.tail();
i += Character.charCount(b);
}

if (i < literal.length()) {
// End of input
if (index < literal.length()) {
// We hit the end of the input
return new Error<String>(Failure.format(current, description()), input);
}

Expand All @@ -121,18 +100,16 @@ public Description description() {

@Override
public Result<String> apply(Input<Character> input) {
input = input.skip();

if (input instanceof CharInput charInput) {
final Matcher matcher = pattern.matcher(charInput);
if (input instanceof CharacterInput sequence) {
final Matcher matcher = pattern.matcher(sequence);
if (matcher.lookingAt()) {
final String value = matcher.group();
return new Success<String>(value, charInput.subSequence(value.length(), charInput.length()));
return new Success<String>(value, sequence.subSequence(value.length(), sequence.length()));
} else {
return new Error<String>(Failure.format(charInput, description()), charInput);
return new Error<String>(Failure.format(sequence, description()), sequence);
}
} else {
// TODO: Collect characters in a buffer and match on this buffer.
// TODO: Collect the characters in a buffer and match on this buffer
throw new UnsupportedOperationException();
}
}
Expand Down
55 changes: 55 additions & 0 deletions core/src/main/java/jcombinators/data/Product.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package jcombinators.data;

import java.util.function.BiFunction;
import java.util.function.Function;
import jcombinators.Parsing.Parser;

/**
* A simple representation of a {@link Product} (or pair) as a record. This class is primarily used by the combinator
* {@link Parser#and} to return two values without losing the type information and to offer convenient operations
* for the mapping and reduction of these two values.
*
* @param first The first or left value of this {@link Product}.
* @param second The second or right value of this {@link Product}.
* @param <A> The type of the first value.
* @param <B> The type of the second value.
*
* @see Parser#and
*/
public record Product<A, B>(A first, B second) {

/**
* Maps this {@link Product} to a new one by mapping its values using the provided functions.
* @param first The function that is used to map the first value of this {@link Product}.
* @param second The function that is used to map the second value of this {@link Product}.
* @return A new {@link Product} whose values correspond to the mapped values of this {@link Product}.
* @param <C> The target type of the first value.
* @param <D> The target type of the second value.
*/
public <C, D> Product<C, D> map(final Function<A, C> first, final Function<B, D> second) {
return of(first.apply(this.first), second.apply(this.second));
}

/**
* Reduces this {@link Product} to a value by applying the provided function to this {@link Product}'s values.
* @param function The function that is used to fold the first and second value to a new value.
* @return The result of applying the provided function to this {@link Product}'s values.
* @param <C> The type of the resulting value.
*/
public <C> C fold(final BiFunction<A, B, C> function) {
return function.apply(first, second);
}

/**
* A shorthand for constructing a new {@link Product}.
* @param first The first or left value of this {@link Product}.
* @param second The second or right value of this {@link Product}.
* @return A new {@link Product} which consists of the provided values.
* @param <A> The type of the first value.
* @param <B> The type of the second value.
*/
public static <A, B> Product<A, B> of(final A first, final B second) {
return new Product<>(first, second);
}

}
20 changes: 0 additions & 20 deletions core/src/main/java/jcombinators/data/Tuple.java

This file was deleted.

Loading

0 comments on commit 278e799

Please sign in to comment.