Skip to content

Commit

Permalink
Track positions for input streams and clean up unnecessary keywords
Browse files Browse the repository at this point in the history
  • Loading branch information
BjoernLoetters committed Jan 13, 2025
1 parent 2abee7f commit b2c8ca6
Show file tree
Hide file tree
Showing 32 changed files with 220 additions and 170 deletions.
62 changes: 35 additions & 27 deletions src/main/java/jcombinators/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import jcombinators.data.Tuple;
import jcombinators.description.Description;
import jcombinators.description.Negation;
import jcombinators.description.Unknown;
import jcombinators.description.Empty;
import jcombinators.input.Input;
import jcombinators.primitive.*;
import jcombinators.result.*;
Expand All @@ -20,56 +20,56 @@
@FunctionalInterface
public interface Parser<T> extends Function<Input, Result<T>> {

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

public default Description description() {
return new Unknown();
default Description description() {
return new Empty();
}

public default Parser<List<T>> repeat() {
default Parser<List<T>> repeat() {
return new RepeatParser<>(this);
}

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

public default <U> Parser<U> map(final Function<T, U> function) {
default <U> Parser<U> map(final Function<T, U> function) {
return new MapParser<>(this, function);
}

public default <U> Parser<U> flatMap(final Function<T, Parser<U>> function) {
default <U> Parser<U> flatMap(final Function<T, Parser<U>> function) {
return new FlatMapParser<>(this, function);
}

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

public default <U> Parser<T> andl(final Parser<U> parser) {
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)));
default <U> Parser<Tuple<T, U>> and(final Parser<U> parser) {
return this.flatMap(first -> parser.map(second -> Tuple.of(first, second)));
}

public default Parser<T> commit() {
default Parser<T> commit() {
return (input -> switch (apply(input)) {
case Success<T> success -> success;
case Error<T> error -> new Abort<>(error.message, error.rest);
case Abort<T> abort -> abort;
});
}

public default Parser<Void> not() {
default Parser<Void> not() {
return (input -> switch (apply(input)) {
case Success<T> success -> new Error<>(Failure.format(input, new Negation(this.description())), input);
case Failure<T> ignore -> new Success<>(null, input);
});
}

public default Parser<Optional<T>> optional() {
default Parser<Optional<T>> optional() {
return (input -> switch(apply(input)) {
case Success<T> success -> new Success<>(Optional.of(success.value), success.rest);
case Error<T> error -> new Success<>(Optional.empty(), error.rest);
Expand All @@ -81,20 +81,20 @@ public default Parser<Optional<T>> optional() {
});
}

public default <U> Parser<List<T>> separate(final Parser<U> separator) {
default <U> Parser<List<T>> separate(final Parser<U> separator) {
return separate1(separator).optional().map(optional -> optional.orElse(List.of()));
}

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

public default Parser<T> or(final Parser<T> alternative) {
default Parser<T> or(final Parser<T> alternative) {
return or(this, alternative);
}

public static <T> Parser<T> chainl(final Parser<T> element, final Parser<BiFunction<T, T, T>> separator) {
static <T> Parser<T> chainl1(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 @@ -106,7 +106,11 @@ public static <T> Parser<T> chainl(final Parser<T> element, final Parser<BiFunct
});
}

public static <T> Parser<T> chainr(final Parser<T> element, final Parser<BiFunction<T, T, T>> separator) {
static <T> Parser<T> chainl(final Parser<T> element, final Parser<BiFunction<T, T, T>> separator, final T otherwise) {
return chainl1(element, separator).optional().map(result -> result.orElse(otherwise));
}

static <T> Parser<T> chainr1(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 All @@ -130,20 +134,24 @@ public static <T> Parser<T> chainr(final Parser<T> element, final Parser<BiFunct
});
}

public static <T> Parser<T> success(final T value) {
static <T> Parser<T> chainr(final Parser<T> element, final Parser<BiFunction<T, T, T>> separator, final T otherwise) {
return chainl1(element, separator).optional().map(result -> result.orElse(otherwise));
}

static <T> Parser<T> success(final T value) {
return (input -> new Success<>(value, input));
}

public static <T> Parser<T> fail(final String message) {
static <T> Parser<T> fail(final String message) {
return (input -> new Error<>(message, input));
}

public static <T> Parser<T> abort(final String message) {
static <T> Parser<T> abort(final String message) {
return (input -> new Abort<>(message, input));
}

@SafeVarargs
public static <T> Parser<T> or(final Parser<? extends T> alternative, final Parser<? extends T> ... alternatives) {
static <T> Parser<T> or(final Parser<? extends T> alternative, final Parser<? extends T>... alternatives) {
@SuppressWarnings("unchecked")
Parser<T> choice = (Parser<T>) alternative;

Expand All @@ -157,7 +165,7 @@ public static <T> Parser<T> or(final Parser<? extends T> alternative, final Pars
}

@SafeVarargs
public static <T> Parser<List<T>> sequence(final Parser<? extends T> ... parsers) {
static <T> Parser<List<T>> sequence(final Parser<? extends T>... parsers) {
final List<Parser<T>> sequence = Stream.of(parsers).map(parser -> {
@SuppressWarnings("unchecked")
final Parser<T> up = (Parser<T>) parser;
Expand Down
17 changes: 16 additions & 1 deletion src/main/java/jcombinators/data/Tuple.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
package jcombinators.data;

public final record Tuple<A, B>(A first, B second) {
import java.util.function.BiFunction;
import java.util.function.Function;

public record Tuple<A, B>(A first, B second) {

public <C, D> Tuple<C, D> map(final Function<A, C> firstFunction, final Function<B, D> secondFunction) {
return of(firstFunction.apply(first), secondFunction.apply(second));
}

public <C> C fold(final BiFunction<A, B, C> function) {
return function.apply(first, second);
}

public static <A, B> Tuple<A, B> of(final A first, final B second) {
return new Tuple<>(first, second);
}

}
7 changes: 3 additions & 4 deletions src/main/java/jcombinators/description/Description.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public sealed abstract class Description permits Choice, Literal, Negation, Regex, Sequence, Unknown {
public sealed abstract class Description permits Choice, Literal, Negation, Regex, Sequence, Empty {

public abstract Optional<String> describe();

Expand Down Expand Up @@ -51,8 +50,8 @@ private static Description normalize(final Description description, final boolea
case Negation negation:
return normalize(negation.description, !negate);

case Unknown unknown:
return unknown;
case Empty empty:
return empty;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import java.util.Optional;

public final class Unknown extends Description {
public final class Empty extends Description {

@Override
public Optional<String> describe() {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/jcombinators/description/Literal.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public Literal(final String literal) {
}

@Override
public final Optional<String> describe() {
public Optional<String> describe() {
if (literal.isEmpty()) {
return Optional.empty();
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/jcombinators/description/Regex.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public Regex(final Pattern pattern) {
}

@Override
public final Optional<String> describe() {
public Optional<String> describe() {
return Optional.of(String.format("an input that matches '%s'", pattern.pattern()));
}

Expand Down
74 changes: 37 additions & 37 deletions src/main/java/jcombinators/input/Input.java
Original file line number Diff line number Diff line change
@@ -1,68 +1,68 @@
package jcombinators.input;

import jcombinators.position.Position;
import jcombinators.position.Specific;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;

public class Input implements CharSequence {
public final class Input {

public final String name;

private final String contents;
public final Position position;

private final int offset;
final String contents;

private final int length;
final int offset;

private Input(final String name, final String contents, final int offset, final int length) {
private Input(final String name, final String contents, final int offset, final int line, final int column) {
this.name = name;
this.contents = contents;
this.position = new Specific(this, line, column);
this.offset = offset;
this.length = length;
}

@Override
public final int length() {
return length;
this.contents = contents;
}

@Override
public final char charAt(final int index) {
if (index < 0 || index >= length) {
throw new IndexOutOfBoundsException(index);
public int getCodePoint() {
if (Character.isHighSurrogate(contents.charAt(offset)) && offset + 1 < contents.length() && Character.isLowSurrogate(contents.charAt(offset + 1))) {
return Character.toCodePoint(contents.charAt(offset), contents.charAt(offset + 1));
} else {
return contents.charAt(offset + index);
return contents.charAt(offset);
}
}

public final int codePointAt(final int index) {
if (index < 0 || index >= length) {
throw new IndexOutOfBoundsException(index);
public Input next() {
if (isEmpty()) {
return this;
} else {
return Character.codePointAt(this, index);
final int line;
final int column;

if (getCodePoint() == '\n') {
line = position.line + 1;
column = 1;
} else {
line = position.line;
column = position.column + 1;
}

return new Input(name, contents, offset + Character.charCount(this.getCodePoint()), line, column);
}
}

@Override
public final Input subSequence(final int start, final int end) {
if (end < 0 || end > length) {
throw new IndexOutOfBoundsException(end);
} else if (start < 0 || start > end) {
throw new IndexOutOfBoundsException(start);
} else {
return new Input(name, contents, offset + start, end - start);
public Input drop(final int number) {
Input result = this;
for (int i = 0; i < number && !result.isEmpty(); ++i) {
result = result.next();
}
return result;
}

public final Input subSequence(final int start) {
return subSequence(start, length);
}

@Override
public final String toString() {
return contents.substring(offset, offset + length);
public boolean isEmpty() {
return offset >= contents.length();
}

public static Input of(final String name, final InputStream stream, final Charset charset) throws IOException {
Expand All @@ -82,7 +82,7 @@ public static Input of(final String name, final byte[] bytes, final Charset char

public static Input of(final String name, final CharSequence sequence) {
final String contents = sequence.toString();
return new Input(name, contents, 0, contents.length());
return new Input(name, contents, 0, 1, 1);
}

}
26 changes: 26 additions & 0 deletions src/main/java/jcombinators/input/InputWrapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package jcombinators.input;

public final class InputWrapper implements CharSequence {

private Input input;

public InputWrapper(final Input input) {
this.input = input;
}

@Override
public int length() {
return input.contents.length() - input.offset;
}

@Override
public char charAt(final int index) {
return input.contents.charAt(index + input.offset);
}

@Override
public CharSequence subSequence(final int start, final int end) {
return input.contents.substring(start + input.offset, end + input.offset);
}

}
7 changes: 6 additions & 1 deletion src/main/java/jcombinators/position/Position.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package jcombinators.position;

import jcombinators.input.Input;

public sealed abstract class Position permits Specific, Unknown {

public final Input input;

public final int line;

public final int column;

public Position(final int line, final int column) {
public Position(final Input input, final int line, final int column) {
this.input = input;
this.line = line;
this.column = column;
}
Expand Down
Loading

0 comments on commit b2c8ca6

Please sign in to comment.