Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
012d1fb
Add custom port of Rust's `Result`
MarcellPerger1 May 3, 2024
8b670aa
Refactor some methods, add more TODOs
MarcellPerger1 May 3, 2024
f9abf20
Refactor a bit
MarcellPerger1 May 4, 2024
a33fdfa
Add more util methods for Result (ifThenElse, toString)
MarcellPerger1 May 4, 2024
2af76a8
Add Result.newOk, Result.newErr
MarcellPerger1 May 5, 2024
77c7076
fix: Add missed `public` to some `Util` methods
MarcellPerger1 May 5, 2024
3348258
refactor: Move Result into `<...>.util.rs` package
MarcellPerger1 May 5, 2024
ebf35e1
test: Start adding tests for Result
MarcellPerger1 May 5, 2024
a30248c
fix: Fix some IntelliJ warnings
MarcellPerger1 May 5, 2024
48793a4
fix: Fix more IntelliJ warnings
MarcellPerger1 May 5, 2024
7a408ad
refactor: Refactor ResultPanicWithValueException
MarcellPerger1 May 5, 2024
84d05dc
refactor: Move base Builder up to PanicException
MarcellPerger1 May 5, 2024
426b5c2
refactor: Remove unneeded SuppressWarnings
MarcellPerger1 May 5, 2024
4b48c01
style: Fix indentation in `.github/dependabot.yml`
MarcellPerger1 May 5, 2024
22fd784
test: Create a small function mocking util (MiniMock.java)
MarcellPerger1 May 5, 2024
517ff33
test(result): Add tests for `map*`, `is*And`
MarcellPerger1 May 5, 2024
64a8ca4
fix: Fix some intellij unused code warnings
MarcellPerger1 May 5, 2024
bc7558a
test: Tests for more Result methods, fix some stuff
MarcellPerger1 May 6, 2024
2b129b2
fix: Fix unwrap_err and add test
MarcellPerger1 May 7, 2024
6aa7497
fix: Fix Result.andThen, Result.orElse
MarcellPerger1 May 7, 2024
767013a
test: Finish the tests
MarcellPerger1 May 7, 2024
0db6968
style: Use camelCase
MarcellPerger1 May 7, 2024
8f1f12d
test: 100% coverage in Result.java!
MarcellPerger1 May 7, 2024
a661ea2
ci: Add coverage test
MarcellPerger1 May 7, 2024
8f84785
refactor: Start making Option (some methods implemented, not all yet)
MarcellPerger1 May 8, 2024
9c6d1f1
refactor: iteration methods on Option
MarcellPerger1 May 8, 2024
23ea424
refactor: Add more methods on Option, fix ambiguous method
MarcellPerger1 May 9, 2024
2375576
refactor: Add more methods on Option (and, or, okOr, filter)
MarcellPerger1 May 9, 2024
5736c50
refactor: Add xor
MarcellPerger1 May 9, 2024
49d4d6f
refactor: Add remaining tests for 100% coverage in `Option`
MarcellPerger1 May 9, 2024
2cd7169
ci: Add Result.java to 100% coverage group
MarcellPerger1 May 9, 2024
cc4171c
refactor: Make Result/Option new* return the base class
MarcellPerger1 May 25, 2024
fb1751f
How does NotNull work on CI - is it checked?
MarcellPerger1 May 25, 2024
df8318f
fix: Remove experiment
MarcellPerger1 May 25, 2024
861ae62
fix: Use ifThenElse_void for Result as well
MarcellPerger1 May 25, 2024
b610ef2
refactor: Add okOption, errOption
MarcellPerger1 May 25, 2024
c03d23f
refactor: Add Option.ofOptional, Option.ofNullable
MarcellPerger1 May 25, 2024
75e0bb3
refactor: Make Mocked* more concise
MarcellPerger1 May 25, 2024
ae7b7cd
refactor: Add Result.fromTry
MarcellPerger1 May 26, 2024
2d78831
refactor: Add Result.fromExc, 'fix' coverage
MarcellPerger1 May 26, 2024
23c8da5
refactor: Start using Result in Parser (parseDoubleLiteral_result) [WIP]
MarcellPerger1 May 26, 2024
332a5fc
refactor: More progress on converting to Result
MarcellPerger1 May 27, 2024
38540de
refactor: Convert back to checked exceptions
MarcellPerger1 May 27, 2024
9b02b60
test: Add tests for the extra 2 methods on Result
MarcellPerger1 May 27, 2024
a62a01a
refactor: Remove old ExprParseRtException.java (we're using checked e…
MarcellPerger1 May 27, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

version: 2
updates:
- package-ecosystem: "devcontainers"
directory: "/"
schedule:
interval: weekly
- package-ecosystem: "devcontainers"
directory: "/"
schedule:
interval: weekly
4 changes: 3 additions & 1 deletion .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ jobs:
distribution: 'temurin'
cache: maven
- name: Test with Maven
run: mvn -B test
run: mvn -B -ntp test
- name: Check coverage
run: mvn -B -ntp verify

# Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
- name: Update dependency graph
Expand Down
16 changes: 16 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 51 additions & 7 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,59 @@
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<failIfNoTests>true</failIfNoTests>
</configuration>
</plugin>
</plugins>
</build>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution>
<id>default-prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>default-report</id>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>default-check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>SOURCEFILE</element>
<includes>
<include>net/marcellperger/mathexpr/util/rs/Result.java</include>
<include>net/marcellperger/mathexpr/util/rs/Option.java</include>
</includes>
<limits>
<limit>
<counter>INSTRUCTION</counter>
<value>COVEREDRATIO</value>
<!-- We have 100% coverage, let's not loose it -->
<minimum>100%</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

This file was deleted.

71 changes: 41 additions & 30 deletions src/main/java/net/marcellperger/mathexpr/parser/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand Down Expand Up @@ -39,38 +40,23 @@ public MathSymbol parseExpr() throws ExprParseException {

// https://regex101.com/r/2EogTA/1
protected static final Pattern DOUBLE_RE = Pattern.compile("^([+-]?)(\\d*\\.\\d+|\\d+\\.?)(?:[eE]([+-]?\\d+))?");
public @NotNull MathSymbol parseDoubleLiteral_exc() throws ExprParseException {
return switch (parseDoubleLiteral_null()) {
case null -> throw new ExprParseException("Couldn't parse double in expression");
case MathSymbol sym -> sym;
};
}
public @Nullable MathSymbol parseDoubleLiteral_null() {
public @NotNull MathSymbol parseDoubleLiteral() throws ExprParseException {
discardWhitespace();
Matcher m = DOUBLE_RE.matcher(strFromHere());
if (!m.lookingAt()) return null;
String s = m.group();
idx += s.length();
double value;
String s = matchNextRegexString(DOUBLE_RE, "Invalid number (double)");
try {
value = Double.parseDouble(s);
} catch (NumberFormatException _exc) {
// Technically this should never happen - assuming I've got that regex right
assert false: "There is a problem with the regex, this should've been rejected earlier";
return null;
return new BasicDoubleSymbol(Double.parseDouble(s));
} catch (NumberFormatException exc) {
throw new AssertionError("There is a problem with the regex," +
" this should've been rejected earlier", exc);
}
return new BasicDoubleSymbol(value);
}

public @Nullable MathSymbol parseParensOrLiteral() throws ExprParseException {
public @NotNull MathSymbol parseParensOrLiteral() throws ExprParseException {
discardWhitespace();
if(peek() == '(') return parseParens();
// TODO add a better error handling system - don't want to maintain 2 versions of each function
// returning null: +easier to do unions, +no need for verbose try/catch, -no info about errors
return parseDoubleLiteral_null();
return peek() == '(' ? parseParens() : parseDoubleLiteral();
}

public @Nullable MathSymbol parseParens() throws ExprParseException {
public MathSymbol parseParens() throws ExprParseException {
advanceExpectNext_ignoreWs('(');
MathSymbol sym = parseExpr();
advanceExpectNext_ignoreWs(')');
Expand Down Expand Up @@ -152,8 +138,7 @@ boolean advanceIf(@NotNull Function<Character, Boolean> predicate) {
* @param predicate Keep advancing while this returns true
* @return Amount of spaces advanced
*/
@SuppressWarnings("UnusedReturnValue")
int advanceWhile(@NotNull Function<Character, Boolean> predicate) {
protected int advanceWhile(@NotNull Function<Character, Boolean> predicate) {
int n = 0;
while (notEof() && advanceIf(predicate)) ++n;
return n;
Expand All @@ -167,19 +152,45 @@ protected void discardWhitespace() {
advanceWhile(Character::isWhitespace);
}

protected void advanceExpectNext(char expected) {
protected void advanceExpectNext(char expected) throws ExprParseException {
char actual = advance();
if(actual != expected) throw new ExprParseRtException("Expected '%c', got '%c'".formatted(expected, actual));
if(actual != expected) throw new ExprParseException("Expected '%c', got '%c'".formatted(expected, actual));
}
protected void advanceExpectNext_ignoreWs(char expected) {
protected void advanceExpectNext_ignoreWs(char expected) throws ExprParseException {
discardWhitespace();
advanceExpectNext(expected);
}

protected MatchResult matchNextRegexResult(@NotNull Pattern pat, ExprParseException exc) throws ExprParseException {
Matcher m = pat.matcher(strFromHere());
if (!m.lookingAt()) throw exc;
String s = m.group();
idx += s.length();
return m.toMatchResult();
}
protected MatchResult matchNextRegexResult(@NotNull Pattern pat, String exc) throws ExprParseException {
return matchNextRegexResult(pat, new ExprParseException(exc));
}
protected MatchResult matchNextRegexResult(@NotNull Pattern pat) throws ExprParseException {
return matchNextRegexResult(pat, "Regex should've been matched");
}
@SuppressWarnings("unused")
protected String matchNextRegexString(@NotNull Pattern pat, ExprParseException exc) throws ExprParseException {
return matchNextRegexResult(pat, exc).group();
}
@SuppressWarnings("SameParameterValue")
protected String matchNextRegexString(@NotNull Pattern pat, String msg) throws ExprParseException {
return matchNextRegexResult(pat, msg).group();
}
@SuppressWarnings("unused")
protected String matchNextRegexString(@NotNull Pattern pat) throws ExprParseException {
return matchNextRegexResult(pat).group();
}

protected boolean matchesNext(@NotNull String expected) {
return src.startsWith(expected, /*start*/idx);
}

private @NotNull List<@NotNull String> sortedByLength(@NotNull List<@NotNull String> arr) {
return arr.stream().sorted(Comparator.comparingInt(String::length).reversed()).toList();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package net.marcellperger.mathexpr.util;

public @interface JacocoIgnoreNotGenerated {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package net.marcellperger.mathexpr.util;

@FunctionalInterface
public interface ThrowingSupplier<T, E extends Throwable> {
T get() throws E;
}
93 changes: 90 additions & 3 deletions src/main/java/net/marcellperger/mathexpr/util/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
import org.jetbrains.annotations.Nullable;

import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;

@SuppressWarnings("unused")
public class Util {
Expand Down Expand Up @@ -56,13 +59,11 @@ protected Util() {}
return obj;
}

@SuppressWarnings("UnusedReturnValue")
@Contract("_ -> param1")
public static <C extends Collection<?>> @NotNull C requireNonEmpty(@NotNull C collection) {
if(collection.isEmpty()) throw new CollectionSizeException("Argument must not be empty");
return collection;
}
@SuppressWarnings("UnusedReturnValue")
@Contract("null -> fail; _ -> param1")
public static <C extends Collection<?>> @NotNull C requireNonEmptyNonNull(C collection) {
return requireNonEmpty(Objects.requireNonNull(collection));
Expand All @@ -72,7 +73,6 @@ protected Util() {}
if(collection.isEmpty()) throw new CollectionSizeException(msg);
return collection;
}
@SuppressWarnings("UnusedReturnValue")
@Contract("null, _ -> fail; _, _ -> param1")
public static <C extends Collection<?>> @NotNull C requireNonEmptyNonNull(C collection, String msg) {
return requireNonEmpty(Objects.requireNonNull(collection), msg);
Expand Down Expand Up @@ -194,4 +194,91 @@ public static<T> T getOnlyItem(@Flow(sourceIsContainer = true) @NotNull Sequence
public static <K, V> @NotNull V getNotNull(@NotNull Map<K, V> map, K key) {
return Objects.requireNonNull(map.get(key));
}

@Contract(value = "_ -> new", pure = true)
public static <T> @NotNull Iterator<T> singleItemIterator(T value) {
return new SingleItemIterator<>(value);
}

protected static class SingleItemIterator<T> implements Iterator<T> {
T value;
boolean isAtEnd;

public SingleItemIterator(T value) {
this.value = value;
isAtEnd = false;
}

@Override
public boolean hasNext() {
return !isAtEnd;
}

@Override
public T next() {
if(isAtEnd) throw new NoSuchElementException("End of SingleItemIterator reached");
return _moveToEndAndPop();
}

protected T _moveToEndAndPop() {
isAtEnd = true;
T val = value;
// Don't hold on to our copy - we don't need it anymore
// as iterators cannot go backwards and this lets JVM free it earlier.
value = null;
return val;
}

@Override
public void forEachRemaining(Consumer<? super T> action) {
if(isAtEnd) return;
action.accept(_moveToEndAndPop());
}
}

@Contract(pure = true)
public static <T> @NotNull Function<? super T, VoidVal> consumerToFunction(Consumer<? super T> consumer) {
return v -> {
consumer.accept(v);
return VoidVal.val();
};
}
@Contract(pure = true)
public static <T> @NotNull UnaryOperator<T> consumerToIdentityFunc(Consumer<? super T> consumer) {
return v -> {
consumer.accept(v);
return v;
};
}

public static @NotNull Supplier<VoidVal> runnableToSupplier(Runnable runnable) {
return () -> {
runnable.run();
return VoidVal.val();
};
}

// These 2 trick Java into throwing checked exceptions in an unchecked way
@Contract("_ -> fail")
public static <T> T throwAsUnchecked(Throwable exc) {
throwAs(exc); // <RuntimeException> is inferred from no `throws` clause
// Java doesn't know that this always throws so this lets us
// do `return/throw throwAsUnchecked()` to make Java's flow control analyser happy
throw new AssertionError("Unreachable");
}
@SuppressWarnings("unchecked")
@Contract("_ -> fail")
public static <E extends Throwable> void throwAs(Throwable exc) throws E {
// We do a little type erasure hack to trick Java:
// - E will be type-erased to Throwable so this will become
// throw (Throwable)exc;
// but exc is already Throwable due to the param type so
// this is like a runtime no-op.
// - But javac will see that we're throwing an E which is allowed!
// - The reason we need a separate method is so that there is a
// generic for javac to type-erase (otherwise JVM would check that
// it's actually that concrete type when it is thrown and this way
// it only checks for the base condition)
throw (E)exc;
}
}
Loading