From 012d1fbc27dc1aa40a1652f034eb52bc624ccad7 Mon Sep 17 00:00:00 2001 From: Marcell Perger Date: Fri, 3 May 2024 23:40:04 +0100 Subject: [PATCH 01/45] Add custom port of Rust's `Result` Implements all methods present on Rust's `Result` type that can actually be implemented in Java and in a more Java-esque style. There were some methods that are impractical / impossible to implement in Java: - `unchecked_*` for obvious reasons - `unwrap_or_default`: no static polymorphism - more info in the big comment - `into_ok`: No infallible/`!` type in Java, not enough stuff at compile time to work (see comment) - The `impl`s on nested `Result` types, etc. as we can only have 1 class-wide constraint (see comment). Although we could make those `static` methods if we really need them. Possibly, we could also make an `Option` class for more convenience. --- .../marcellperger/mathexpr/util/Result.java | 334 ++++++++++++++++++ .../net/marcellperger/mathexpr/util/Util.java | 50 +++ .../marcellperger/mathexpr/util/VoidVal.java | 20 ++ 3 files changed, 404 insertions(+) create mode 100644 src/main/java/net/marcellperger/mathexpr/util/Result.java create mode 100644 src/main/java/net/marcellperger/mathexpr/util/VoidVal.java diff --git a/src/main/java/net/marcellperger/mathexpr/util/Result.java b/src/main/java/net/marcellperger/mathexpr/util/Result.java new file mode 100644 index 0000000..8dd6a7b --- /dev/null +++ b/src/main/java/net/marcellperger/mathexpr/util/Result.java @@ -0,0 +1,334 @@ +package net.marcellperger.mathexpr.util; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Stream; + +// TODO maybe separate (sub-)package? + + +/** + * An implementation of Rust's immutable {@code Result} type that stores either: + * + * No restrictions are placed on any of these types for convenience and flexibility. + * @see Rust's std::result::Result + * @param Type of the {@link Ok} value + * @param Type of the {@link Err} error value + */ +@SuppressWarnings({"unused", "InnerClassOfInterface"}) // should be an abstract class but then the `record Ok/Err` cannot extend it +public sealed interface Result extends Iterable { + /* + impl Result for Ok { ... } + + impl Result for Err { ... } + + */ + record Ok(T value) implements Result { + @Override + public Ok ok() { return this; } + + public Ok cast() { return new Ok<>(value); } + } + // Ok == Ok + // void f(Ok arg); + // f(new Ok(...).cast()) + + record Err(E exc) implements Result { + public Err err() { return this; } + + public Err cast() { return new Err<>(exc); } + } + + default @Nullable Ok ok() { return null; } + default @Nullable Err err() { return null; } + + default Optional> okOpt() { return Optional.ofNullable(ok()); } + default Optional> errOpt() { return Optional.ofNullable(err()); } + + + default boolean isOk() { return ok() != null; } + default boolean isErr() { return err() != null; } + + default boolean isOkAnd(Predicate f) { + return switch (this) { + case Ok(T value) -> f.test(value); + case Err e -> false; + }; + } + default boolean isErrAnd(Predicate f) { + return switch (this) { + case Ok o -> false; + case Err(E err) -> f.test(err); + }; + } + + // TODO: once Option is done, do proper ok() / err() -> Option + + default Result map(Function op) { + return switch (this) { + case Ok(T value) -> new Ok<>(op.apply(value)); + case Err e -> e.cast(); + }; + } + // TODO some sort of Consumer<> variant of these or a + // (IMO more logically named) valueOrElse / tryElse / ifLetElse / andThenElse + default U mapOr(U default_, Function f) { + return switch (this) { + case Ok(T value) -> f.apply(value); + case Err _err -> default_; + }; + } + default U mapOrElse(Function defaultFn, Function f) { + return switch (this) { + case Ok(T value) -> f.apply(value); + case Err(E err) -> defaultFn.apply(err); + }; + } + default Result mapErr(Function op) { + return switch (this) { + case Err(E err) -> new Err<>(op.apply(err)); + case Ok o -> o.cast(); + }; + } + + default Result inspect(Consumer f) { + return map(value -> { + f.accept(value); + return value; + }); + } + default Result inspectErr(Consumer f) { + return mapErr(exc -> { + f.accept(exc); + return exc; + }); + } + + // .iter()-esque methods: why does Java have SO MANY - one is enough. + default Stream stream() { + return okOpt().map(Ok::value).stream(); + } + // Iterable automatically implements `spliterator()` for us + @NotNull + @Override + default Iterator iterator() { + return switch (this) { + case Ok(T value) -> Util.singleItemIterator(value); + case Err(E _err) -> Collections.emptyIterator(); + }; + } + @Override + default void forEach(Consumer action) { + map(Util.consumerToFunction(action)); + } + + default T unwrap() { + return expect("unwrap() got Err value"); + } + default T expect(String msg) { + return okOpt().orElseThrow(() -> ResultPanicWithValueException.fromMaybeExcValue(err(), msg)).value; + } + // We CANNOT do unwrap_or_default because Java: + // - Static methods cannot be overloaded properly and cannot even be part of an interface + // (static methods are very much NOT first-class things in Java - unlike Rust) + // so the language that is the 'standard' Object-Oriented language cannot + // even do polymorphism for static methods! (without resorting to + // runtime non-compile-time-checked reflection) + // - My (very many) attempts to solve it using C++-style CRTP have all failed + // because Java handles generics using type erasure, making 1 general + // type-erased version of a method so any attempts to call methods on the passed subtype + // just results on the method being called on the type declared extended in the ``. + // Another problematic consequence of (I think) type erasure is that a class hierarchy + // cannot implement the same interface with 2 different types so something + // cannot be both Iterable and Iterable. + // IMO, handling generics using monomorphisation (like C++ and Rust) would solve these issues and would + // allow much greater flexibility and could open paths to a SFINAE-type + // system which would solve the first BP. + // - I really don't want to use reflection, because in strongly typed languages, + // my philosophy is "Check as much as possible at compile-time to reduce runtime failure". + default E expect_err(String msg) { + return errOpt().orElseThrow(() -> ResultPanicWithValueException.fromPlainValue(ok(), msg)).exc; + } + default E unwrap_err() { + return expect_err("unwrap_err() got Ok value"); + } + // We can't implement into_ok/into_err as Java doesn't have that + // kind of infallible type and I don't think Java can be that rigorous about + // infallibility (Java's type system in proven to be unsound so...) + // or can't even reason about it in the first place. + + default Result and(Result other) { + return switch (this) { + case Ok _ok -> other; // everything normal, return right + case Err err -> err.cast(); + }; + } + default Result andThen(Supplier> then) { + return switch (this) { + case Ok _ok -> then.get(); + case Err err -> err.cast(); + }; + } + + default Result or(Result other) { + return switch (this) { + case Ok ok -> ok.cast(); + case Err _err -> other; + }; + } + default Result orElse(Supplier> elseFn) { + return switch (this) { + case Ok ok -> ok.cast(); + case Err _err -> elseFn.get(); // `this` failed so try `elseFn` - "You're my only hope" + }; + } + + default T unwrapOr(T defaultV) { + return mapOr(defaultV, Function.identity()); + } + default T unwrapOrElse(Function ifErr) { + return mapOrElse(ifErr, Function.identity()); + } + // no unwrap_unchecked/unwrap_err_unchecked for obvious reasons (usually Java != UB/unsafe) + + // Cannot have implementations for Result, E> and similar because Java + // has nothing that allows us to enable methods if certain conditions are met + // and no multiple implementation blocks with possibly-different conditions on the type parameters. + + // TODO maybe these exceptions deserve their own file... + class ResultPanicException extends RuntimeException { + public ResultPanicException() { + } + public ResultPanicException(String message) { + super(message); + } + public ResultPanicException(String message, Throwable cause) { + super(message, cause); + } + public ResultPanicException(Throwable cause) { + super(cause); + } + + @Contract(value = " -> new", pure = true) + public static @NotNull Builder builder() { + return new Builder(); + } + + public static class Builder { + protected @Nullable String msg; + protected @Nullable Throwable cause; + + protected Builder() { + msg = null; + cause = null; + } + + @Contract("_ -> this") + public Builder msg(@Nullable String msg) { + this.msg = msg; + return this; + } + @Contract("_ -> this") + public Builder cause(@Nullable Throwable cause) { + this.cause = cause; + return this; + } + + public ResultPanicException build() { + return msg != null + ? cause != null ? new ResultPanicException(msg, cause) : new ResultPanicException(msg) + : cause != null ? new ResultPanicException(cause) : new ResultPanicException(); + } + } + } + + // Can't make `Throwable`s generic because java... + class ResultPanicWithValueException extends ResultPanicException { + protected @Nullable Object value; + + /// To set the cause, use `.builder()` + public ResultPanicWithValueException() { + } + + public ResultPanicWithValueException(String message) { + super(message); + } + + public ResultPanicWithValueException(String message, Throwable cause) { + super(message, cause); + } + public ResultPanicWithValueException(String message, Throwable cause, @Nullable Object value) { + super(message, cause); + this.value = value; + } + + public ResultPanicWithValueException(Throwable cause) { + super(cause); + } + + public static @NotNull ResultPanicWithValueException fromMaybeExcValue(@Nullable Object value, String msg) { + return switch (value) { + case null -> new ResultPanicWithValueException(msg); + case Throwable excValue -> new ResultPanicWithValueException(msg, excValue, excValue); + default -> new ResultPanicWithValueException(msg, null, value); + }; + } + public static @NotNull ResultPanicWithValueException fromPlainValue(@Nullable Object value, String msg) { + ResultPanicWithValueException res = new ResultPanicWithValueException(msg); + res.setValue(value); + return res; + } + + @Override + public String getMessage() { + return switch (super.getMessage()) { + case null -> value != null ? "Panic value: " + value : ""; + case "" -> value != null ? "Panic value: " + value : ""; + case String msg -> msg + (value == null ? "" : ": " + value); + }; + } + + protected ResultPanicWithValueException(@NotNull ResultPanicException parent) { + this(parent.getMessage(), parent.getCause()); + } + + public @Nullable Object getValue() { + return value; + } + public void setValue(@Nullable Object value) { + this.value = value; + } + + public static class Builder extends ResultPanicException.Builder { + protected @Nullable Object value; + + public Builder() { + super(); + value = null; + } + + @Contract("_ -> this") + public Builder value(Object value) { + this.value = value; + return this; + } + + @Override + public ResultPanicWithValueException build() { + ResultPanicWithValueException ret = new ResultPanicWithValueException(super.build()); + ret.setValue(value); + return ret; + } + } + } +} diff --git a/src/main/java/net/marcellperger/mathexpr/util/Util.java b/src/main/java/net/marcellperger/mathexpr/util/Util.java index 14f28cb..bcfa3a4 100644 --- a/src/main/java/net/marcellperger/mathexpr/util/Util.java +++ b/src/main/java/net/marcellperger/mathexpr/util/Util.java @@ -7,6 +7,7 @@ import org.jetbrains.annotations.Nullable; import java.util.*; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -194,4 +195,53 @@ public static T getOnlyItem(@Flow(sourceIsContainer = true) @NotNull Sequence public static @NotNull V getNotNull(@NotNull Map map, K key) { return Objects.requireNonNull(map.get(key)); } + + @Contract(value = "_ -> new", pure = true) + public static @NotNull Iterator singleItemIterator(T value) { + return new SingleItemIterator<>(value); + } + + protected static class SingleItemIterator implements Iterator { + 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 action) { + if(isAtEnd) return; + action.accept(_moveToEndAndPop()); + } + } + + @Contract(pure = true) + static @NotNull Function consumerToFunction(Consumer consumer) { + return v -> { + consumer.accept(v); + return VoidVal.val(); + }; + } } diff --git a/src/main/java/net/marcellperger/mathexpr/util/VoidVal.java b/src/main/java/net/marcellperger/mathexpr/util/VoidVal.java new file mode 100644 index 0000000..c0db270 --- /dev/null +++ b/src/main/java/net/marcellperger/mathexpr/util/VoidVal.java @@ -0,0 +1,20 @@ +package net.marcellperger.mathexpr.util; + +import org.jetbrains.annotations.NotNull; + + +/** The equivalent of Rust's {@code ()} or Python's {@code NoneType}/{@code None} + * or a {@code void} type in Java but can be passed as a type arg + * when e.g. you don't actually want to return anything but {@code Function} interface + * requires it to return instances of a type. Because 'no value' is a type that has + * EXACTLY ONE instance: nothing ({@code None} / {@code ()} / {@code undefined})*/ +@SuppressWarnings("InstantiationOfUtilityClass") // not a so-called 'utility class'; Pycharm just thinks it is +public final class VoidVal { + static @NotNull VoidVal INST = new VoidVal(); + + private VoidVal() {} + + public static VoidVal val() { + return INST; + } +} From 8b670aae73b21d2f019e295a9c20e40315158ae4 Mon Sep 17 00:00:00 2001 From: Marcell Perger Date: Fri, 3 May 2024 23:50:57 +0100 Subject: [PATCH 02/45] Refactor some methods, add more TODOs --- .../net/marcellperger/mathexpr/util/Result.java | 14 +++++--------- .../java/net/marcellperger/mathexpr/util/Util.java | 10 +++++++++- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/main/java/net/marcellperger/mathexpr/util/Result.java b/src/main/java/net/marcellperger/mathexpr/util/Result.java index 8dd6a7b..3470fa6 100644 --- a/src/main/java/net/marcellperger/mathexpr/util/Result.java +++ b/src/main/java/net/marcellperger/mathexpr/util/Result.java @@ -101,17 +101,13 @@ default Result mapErr(Function op) { }; } + // TODO toString + default Result inspect(Consumer f) { - return map(value -> { - f.accept(value); - return value; - }); + return map(Util.consumerToIdentityFunc(f)); } default Result inspectErr(Consumer f) { - return mapErr(exc -> { - f.accept(exc); - return exc; - }); + return mapErr(Util.consumerToIdentityFunc(f)); } // .iter()-esque methods: why does Java have SO MANY - one is enough. @@ -178,7 +174,7 @@ default Result andThen(Supplier> then) { case Ok _ok -> then.get(); case Err err -> err.cast(); }; - } + } // TODO refactor these add/andThen methods /similar to use common functionality default Result or(Result other) { return switch (this) { diff --git a/src/main/java/net/marcellperger/mathexpr/util/Util.java b/src/main/java/net/marcellperger/mathexpr/util/Util.java index bcfa3a4..888fa80 100644 --- a/src/main/java/net/marcellperger/mathexpr/util/Util.java +++ b/src/main/java/net/marcellperger/mathexpr/util/Util.java @@ -10,6 +10,7 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; +import java.util.function.UnaryOperator; @SuppressWarnings("unused") public class Util { @@ -238,10 +239,17 @@ public void forEachRemaining(Consumer action) { } @Contract(pure = true) - static @NotNull Function consumerToFunction(Consumer consumer) { + static @NotNull Function consumerToFunction(Consumer consumer) { return v -> { consumer.accept(v); return VoidVal.val(); }; } + @Contract(pure = true) + static @NotNull UnaryOperator consumerToIdentityFunc(Consumer consumer) { + return v -> { + consumer.accept(v); + return v; + }; + } } From f9abf206fbc7ce8f260c490433f97f4ae08d1d57 Mon Sep 17 00:00:00 2001 From: Marcell Perger Date: Sat, 4 May 2024 11:09:26 +0100 Subject: [PATCH 03/45] Refactor a bit --- .../marcellperger/mathexpr/util/Result.java | 49 +++++++------------ 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/src/main/java/net/marcellperger/mathexpr/util/Result.java b/src/main/java/net/marcellperger/mathexpr/util/Result.java index 3470fa6..b6f868b 100644 --- a/src/main/java/net/marcellperger/mathexpr/util/Result.java +++ b/src/main/java/net/marcellperger/mathexpr/util/Result.java @@ -27,30 +27,26 @@ */ @SuppressWarnings({"unused", "InnerClassOfInterface"}) // should be an abstract class but then the `record Ok/Err` cannot extend it public sealed interface Result extends Iterable { - /* - impl Result for Ok { ... } - - impl Result for Err { ... } - - */ record Ok(T value) implements Result { - @Override - public Ok ok() { return this; } - public Ok cast() { return new Ok<>(value); } } - // Ok == Ok - // void f(Ok arg); - // f(new Ok(...).cast()) record Err(E exc) implements Result { - public Err err() { return this; } - public Err cast() { return new Err<>(exc); } } - default @Nullable Ok ok() { return null; } - default @Nullable Err err() { return null; } + default @Nullable Ok ok() { + return switch (this) { + case Ok ok -> ok; + case Err _e -> null; + }; + } + default @Nullable Err err() { + return switch (this) { + case Ok _ok -> null; + case Err err -> err; + }; + } default Optional> okOpt() { return Optional.ofNullable(ok()); } default Optional> errOpt() { return Optional.ofNullable(err()); } @@ -83,10 +79,7 @@ default Result map(Function op) { // TODO some sort of Consumer<> variant of these or a // (IMO more logically named) valueOrElse / tryElse / ifLetElse / andThenElse default U mapOr(U default_, Function f) { - return switch (this) { - case Ok(T value) -> f.apply(value); - case Err _err -> default_; - }; + return mapOrElse((_e) -> default_, f); } default U mapOrElse(Function defaultFn, Function f) { return switch (this) { @@ -96,8 +89,8 @@ default U mapOrElse(Function defaultFn, Function Result mapErr(Function op) { return switch (this) { - case Err(E err) -> new Err<>(op.apply(err)); case Ok o -> o.cast(); + case Err(E err) -> new Err<>(op.apply(err)); }; } @@ -164,23 +157,17 @@ default E unwrap_err() { // or can't even reason about it in the first place. default Result and(Result other) { - return switch (this) { - case Ok _ok -> other; // everything normal, return right - case Err err -> err.cast(); - }; + return andThen(() -> other); } default Result andThen(Supplier> then) { return switch (this) { - case Ok _ok -> then.get(); + case Ok _ok -> then.get(); // everything normal with `this`, do next case Err err -> err.cast(); }; - } // TODO refactor these add/andThen methods /similar to use common functionality + } default Result or(Result other) { - return switch (this) { - case Ok ok -> ok.cast(); - case Err _err -> other; - }; + return orElse(() -> other); } default Result orElse(Supplier> elseFn) { return switch (this) { From a33fdfaa6db0ab40b95a8b3fb18c806a7d0cf1ba Mon Sep 17 00:00:00 2001 From: Marcell Perger Date: Sat, 4 May 2024 13:36:02 +0100 Subject: [PATCH 04/45] Add more util methods for Result (ifThenElse, toString) --- .../marcellperger/mathexpr/util/Result.java | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/marcellperger/mathexpr/util/Result.java b/src/main/java/net/marcellperger/mathexpr/util/Result.java index b6f868b..ed0ad22 100644 --- a/src/main/java/net/marcellperger/mathexpr/util/Result.java +++ b/src/main/java/net/marcellperger/mathexpr/util/Result.java @@ -27,14 +27,26 @@ */ @SuppressWarnings({"unused", "InnerClassOfInterface"}) // should be an abstract class but then the `record Ok/Err` cannot extend it public sealed interface Result extends Iterable { + // hashCode / equals is automatically implemented for these record types, but we customize toString record Ok(T value) implements Result { public Ok cast() { return new Ok<>(value); } + + @Override + public String toString() { + return "Ok(" + value + ')'; + } } record Err(E exc) implements Result { public Err cast() { return new Err<>(exc); } + + @Override + public String toString() { + return "Err(" + exc + ')'; + } } + default @Nullable Ok ok() { return switch (this) { case Ok ok -> ok; @@ -76,8 +88,6 @@ default Result map(Function op) { case Err e -> e.cast(); }; } - // TODO some sort of Consumer<> variant of these or a - // (IMO more logically named) valueOrElse / tryElse / ifLetElse / andThenElse default U mapOr(U default_, Function f) { return mapOrElse((_e) -> default_, f); } @@ -94,7 +104,13 @@ default Result mapErr(Function op) { }; } - // TODO toString + // not a Rust function. Similar to mapOrElse but reversed arguments + default void ifThenElse(Consumer okFn, Consumer errFn) { + mapOrElse(Util.consumerToFunction(errFn), Util.consumerToFunction(okFn)); + } + default U ifThenElse(Function okFn, Function errFn) { + return mapOrElse(errFn, okFn); + } default Result inspect(Consumer f) { return map(Util.consumerToIdentityFunc(f)); From 2af76a838aeadc50a3564c6827754d736bc21ced Mon Sep 17 00:00:00 2001 From: Marcell Perger Date: Sun, 5 May 2024 18:02:16 +0100 Subject: [PATCH 05/45] Add Result.newOk, Result.newErr --- src/main/java/net/marcellperger/mathexpr/util/Result.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/net/marcellperger/mathexpr/util/Result.java b/src/main/java/net/marcellperger/mathexpr/util/Result.java index ed0ad22..d4a647a 100644 --- a/src/main/java/net/marcellperger/mathexpr/util/Result.java +++ b/src/main/java/net/marcellperger/mathexpr/util/Result.java @@ -46,6 +46,13 @@ public String toString() { } } + static Ok newOk(T value) { + return new Ok<>(value); + } + static Err newErr(E err) { + return new Err<>(err); + } + default @Nullable Ok ok() { return switch (this) { From 77c70762fd9757b9d5a78c1376642366eac1e59d Mon Sep 17 00:00:00 2001 From: Marcell Perger Date: Sun, 5 May 2024 18:06:13 +0100 Subject: [PATCH 06/45] fix: Add missed `public` to some `Util` methods --- src/main/java/net/marcellperger/mathexpr/util/Util.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/marcellperger/mathexpr/util/Util.java b/src/main/java/net/marcellperger/mathexpr/util/Util.java index 888fa80..cae5ab3 100644 --- a/src/main/java/net/marcellperger/mathexpr/util/Util.java +++ b/src/main/java/net/marcellperger/mathexpr/util/Util.java @@ -239,14 +239,14 @@ public void forEachRemaining(Consumer action) { } @Contract(pure = true) - static @NotNull Function consumerToFunction(Consumer consumer) { + public static @NotNull Function consumerToFunction(Consumer consumer) { return v -> { consumer.accept(v); return VoidVal.val(); }; } @Contract(pure = true) - static @NotNull UnaryOperator consumerToIdentityFunc(Consumer consumer) { + public static @NotNull UnaryOperator consumerToIdentityFunc(Consumer consumer) { return v -> { consumer.accept(v); return v; From 33482580d2a44ff4a1ea50a30cd4b064dd145638 Mon Sep 17 00:00:00 2001 From: Marcell Perger Date: Sun, 5 May 2024 18:15:29 +0100 Subject: [PATCH 07/45] refactor: Move Result into `<...>.util.rs` package --- .../mathexpr/util/rs/PanicException.java | 18 +++ .../mathexpr/util/{ => rs}/Result.java | 133 +----------------- .../util/rs/ResultPanicException.java | 55 ++++++++ .../rs/ResultPanicWithValueException.java | 87 ++++++++++++ 4 files changed, 162 insertions(+), 131 deletions(-) create mode 100644 src/main/java/net/marcellperger/mathexpr/util/rs/PanicException.java rename src/main/java/net/marcellperger/mathexpr/util/{ => rs}/Result.java (65%) create mode 100644 src/main/java/net/marcellperger/mathexpr/util/rs/ResultPanicException.java create mode 100644 src/main/java/net/marcellperger/mathexpr/util/rs/ResultPanicWithValueException.java diff --git a/src/main/java/net/marcellperger/mathexpr/util/rs/PanicException.java b/src/main/java/net/marcellperger/mathexpr/util/rs/PanicException.java new file mode 100644 index 0000000..fc41512 --- /dev/null +++ b/src/main/java/net/marcellperger/mathexpr/util/rs/PanicException.java @@ -0,0 +1,18 @@ +package net.marcellperger.mathexpr.util.rs; + +public class PanicException extends RuntimeException { + public PanicException() { + } + + public PanicException(String message) { + super(message); + } + + public PanicException(String message, Throwable cause) { + super(message, cause); + } + + public PanicException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/net/marcellperger/mathexpr/util/Result.java b/src/main/java/net/marcellperger/mathexpr/util/rs/Result.java similarity index 65% rename from src/main/java/net/marcellperger/mathexpr/util/Result.java rename to src/main/java/net/marcellperger/mathexpr/util/rs/Result.java index d4a647a..4333434 100644 --- a/src/main/java/net/marcellperger/mathexpr/util/Result.java +++ b/src/main/java/net/marcellperger/mathexpr/util/rs/Result.java @@ -1,6 +1,6 @@ -package net.marcellperger.mathexpr.util; +package net.marcellperger.mathexpr.util.rs; -import org.jetbrains.annotations.Contract; +import net.marcellperger.mathexpr.util.Util; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -13,8 +13,6 @@ import java.util.function.Supplier; import java.util.stream.Stream; -// TODO maybe separate (sub-)package? - /** * An implementation of Rust's immutable {@code Result} type that stores either: @@ -210,131 +208,4 @@ default T unwrapOrElse(Function ifErr) { // Cannot have implementations for Result, E> and similar because Java // has nothing that allows us to enable methods if certain conditions are met // and no multiple implementation blocks with possibly-different conditions on the type parameters. - - // TODO maybe these exceptions deserve their own file... - class ResultPanicException extends RuntimeException { - public ResultPanicException() { - } - public ResultPanicException(String message) { - super(message); - } - public ResultPanicException(String message, Throwable cause) { - super(message, cause); - } - public ResultPanicException(Throwable cause) { - super(cause); - } - - @Contract(value = " -> new", pure = true) - public static @NotNull Builder builder() { - return new Builder(); - } - - public static class Builder { - protected @Nullable String msg; - protected @Nullable Throwable cause; - - protected Builder() { - msg = null; - cause = null; - } - - @Contract("_ -> this") - public Builder msg(@Nullable String msg) { - this.msg = msg; - return this; - } - @Contract("_ -> this") - public Builder cause(@Nullable Throwable cause) { - this.cause = cause; - return this; - } - - public ResultPanicException build() { - return msg != null - ? cause != null ? new ResultPanicException(msg, cause) : new ResultPanicException(msg) - : cause != null ? new ResultPanicException(cause) : new ResultPanicException(); - } - } - } - - // Can't make `Throwable`s generic because java... - class ResultPanicWithValueException extends ResultPanicException { - protected @Nullable Object value; - - /// To set the cause, use `.builder()` - public ResultPanicWithValueException() { - } - - public ResultPanicWithValueException(String message) { - super(message); - } - - public ResultPanicWithValueException(String message, Throwable cause) { - super(message, cause); - } - public ResultPanicWithValueException(String message, Throwable cause, @Nullable Object value) { - super(message, cause); - this.value = value; - } - - public ResultPanicWithValueException(Throwable cause) { - super(cause); - } - - public static @NotNull ResultPanicWithValueException fromMaybeExcValue(@Nullable Object value, String msg) { - return switch (value) { - case null -> new ResultPanicWithValueException(msg); - case Throwable excValue -> new ResultPanicWithValueException(msg, excValue, excValue); - default -> new ResultPanicWithValueException(msg, null, value); - }; - } - public static @NotNull ResultPanicWithValueException fromPlainValue(@Nullable Object value, String msg) { - ResultPanicWithValueException res = new ResultPanicWithValueException(msg); - res.setValue(value); - return res; - } - - @Override - public String getMessage() { - return switch (super.getMessage()) { - case null -> value != null ? "Panic value: " + value : ""; - case "" -> value != null ? "Panic value: " + value : ""; - case String msg -> msg + (value == null ? "" : ": " + value); - }; - } - - protected ResultPanicWithValueException(@NotNull ResultPanicException parent) { - this(parent.getMessage(), parent.getCause()); - } - - public @Nullable Object getValue() { - return value; - } - public void setValue(@Nullable Object value) { - this.value = value; - } - - public static class Builder extends ResultPanicException.Builder { - protected @Nullable Object value; - - public Builder() { - super(); - value = null; - } - - @Contract("_ -> this") - public Builder value(Object value) { - this.value = value; - return this; - } - - @Override - public ResultPanicWithValueException build() { - ResultPanicWithValueException ret = new ResultPanicWithValueException(super.build()); - ret.setValue(value); - return ret; - } - } - } } diff --git a/src/main/java/net/marcellperger/mathexpr/util/rs/ResultPanicException.java b/src/main/java/net/marcellperger/mathexpr/util/rs/ResultPanicException.java new file mode 100644 index 0000000..c866a1d --- /dev/null +++ b/src/main/java/net/marcellperger/mathexpr/util/rs/ResultPanicException.java @@ -0,0 +1,55 @@ +package net.marcellperger.mathexpr.util.rs; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class ResultPanicException extends RuntimeException { + public ResultPanicException() { + } + + public ResultPanicException(String message) { + super(message); + } + + public ResultPanicException(String message, Throwable cause) { + super(message, cause); + } + + public ResultPanicException(Throwable cause) { + super(cause); + } + + @Contract(value = " -> new", pure = true) + public static @NotNull Builder builder() { + return new Builder(); + } + + public static class Builder { + protected @Nullable String msg; + protected @Nullable Throwable cause; + + protected Builder() { + msg = null; + cause = null; + } + + @Contract("_ -> this") + public Builder msg(@Nullable String msg) { + this.msg = msg; + return this; + } + + @Contract("_ -> this") + public Builder cause(@Nullable Throwable cause) { + this.cause = cause; + return this; + } + + public ResultPanicException build() { + return msg != null + ? cause != null ? new ResultPanicException(msg, cause) : new ResultPanicException(msg) + : cause != null ? new ResultPanicException(cause) : new ResultPanicException(); + } + } +} diff --git a/src/main/java/net/marcellperger/mathexpr/util/rs/ResultPanicWithValueException.java b/src/main/java/net/marcellperger/mathexpr/util/rs/ResultPanicWithValueException.java new file mode 100644 index 0000000..32e31f2 --- /dev/null +++ b/src/main/java/net/marcellperger/mathexpr/util/rs/ResultPanicWithValueException.java @@ -0,0 +1,87 @@ +package net.marcellperger.mathexpr.util.rs; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +// Can't make `Throwable`s generic because java... +public class ResultPanicWithValueException extends ResultPanicException { + protected @Nullable Object value; + + /// To set the cause, use `.builder()` + public ResultPanicWithValueException() { + } + + public ResultPanicWithValueException(String message) { + super(message); + } + + public ResultPanicWithValueException(String message, Throwable cause) { + super(message, cause); + } + + public ResultPanicWithValueException(String message, Throwable cause, @Nullable Object value) { + super(message, cause); + this.value = value; + } + + public ResultPanicWithValueException(Throwable cause) { + super(cause); + } + + protected ResultPanicWithValueException(@NotNull ResultPanicException parent) { + this(parent.getMessage(), parent.getCause()); + } + + public static @NotNull ResultPanicWithValueException fromMaybeExcValue(@Nullable Object value, String msg) { + return switch (value) { + case null -> new ResultPanicWithValueException(msg); + case Throwable excValue -> new ResultPanicWithValueException(msg, excValue, excValue); + default -> new ResultPanicWithValueException(msg, null, value); + }; + } + + public static @NotNull ResultPanicWithValueException fromPlainValue(@Nullable Object value, String msg) { + ResultPanicWithValueException res = new ResultPanicWithValueException(msg); + res.setValue(value); + return res; + } + + @Override + public String getMessage() { + return switch (super.getMessage()) { + case null -> value != null ? "Panic value: " + value : ""; + case "" -> value != null ? "Panic value: " + value : ""; + case String msg -> msg + (value == null ? "" : ": " + value); + }; + } + + public @Nullable Object getValue() { + return value; + } + public void setValue(@Nullable Object value) { + this.value = value; + } + + public static class Builder extends ResultPanicException.Builder { + protected @Nullable Object value; + + public Builder() { + super(); + value = null; + } + + @Contract("_ -> this") + public Builder value(Object value) { + this.value = value; + return this; + } + + @Override + public ResultPanicWithValueException build() { + ResultPanicWithValueException ret = new ResultPanicWithValueException(super.build()); + ret.setValue(value); + return ret; + } + } +} From ebf35e13ef5da6b529745c56214aefc307b78de0 Mon Sep 17 00:00:00 2001 From: Marcell Perger Date: Sun, 5 May 2024 18:27:32 +0100 Subject: [PATCH 08/45] test: Start adding tests for Result --- .../mathexpr/util/rs/ResultTest.java | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 src/test/java/net/marcellperger/mathexpr/util/rs/ResultTest.java diff --git a/src/test/java/net/marcellperger/mathexpr/util/rs/ResultTest.java b/src/test/java/net/marcellperger/mathexpr/util/rs/ResultTest.java new file mode 100644 index 0000000..2d829a1 --- /dev/null +++ b/src/test/java/net/marcellperger/mathexpr/util/rs/ResultTest.java @@ -0,0 +1,118 @@ +package net.marcellperger.mathexpr.util.rs; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ResultTest { + Result getOk() { + return Result.newOk(314); + } + Result getErr() { + return Result.newErr("TESTING_ERROR"); + } + + @Test + void isOk() { + assertTrue(getOk().isOk()); + assertFalse(getErr().isOk()); + } + + @Test + void isErr() { + assertFalse(getOk().isErr()); + assertTrue(getErr().isErr()); + } + + @Test + void isOkAnd() { + } + + @Test + void isErrAnd() { + } + + @Test + void map() { + } + + @Test + void mapOr() { + } + + @Test + void mapOrElse() { + } + + @Test + void mapErr() { + } + + @Test + void ifThenElse() { + } + + @Test + void testIfThenElse() { + } + + @Test + void inspect() { + } + + @Test + void inspectErr() { + } + + @Test + void stream() { + } + + @Test + void iterator() { + } + + @Test + void forEach() { + } + + @Test + void unwrap() { + } + + @Test + void expect() { + } + + @Test + void expect_err() { + } + + @Test + void unwrap_err() { + } + + @Test + void and() { + } + + @Test + void andThen() { + } + + @Test + void or() { + } + + @Test + void orElse() { + } + + @Test + void unwrapOr() { + } + + @Test + void unwrapOrElse() { + } +} \ No newline at end of file From a30248c3face598c66e4cdfc55b643ad75439661 Mon Sep 17 00:00:00 2001 From: Marcell Perger Date: Sun, 5 May 2024 18:47:46 +0100 Subject: [PATCH 09/45] fix: Fix some IntelliJ warnings --- .idea/inspectionProfiles/Project_Default.xml | 4 ++++ src/main/java/net/marcellperger/mathexpr/parser/Parser.java | 3 +-- .../net/marcellperger/mathexpr/util/rs/PanicException.java | 1 + .../marcellperger/mathexpr/util/rs/ResultPanicException.java | 1 + 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index e01daa3..69f1877 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -2,5 +2,9 @@ \ No newline at end of file diff --git a/src/main/java/net/marcellperger/mathexpr/parser/Parser.java b/src/main/java/net/marcellperger/mathexpr/parser/Parser.java index 2c13958..867c849 100644 --- a/src/main/java/net/marcellperger/mathexpr/parser/Parser.java +++ b/src/main/java/net/marcellperger/mathexpr/parser/Parser.java @@ -152,8 +152,7 @@ boolean advanceIf(@NotNull Function predicate) { * @param predicate Keep advancing while this returns true * @return Amount of spaces advanced */ - @SuppressWarnings("UnusedReturnValue") - int advanceWhile(@NotNull Function predicate) { + protected int advanceWhile(@NotNull Function predicate) { int n = 0; while (notEof() && advanceIf(predicate)) ++n; return n; diff --git a/src/main/java/net/marcellperger/mathexpr/util/rs/PanicException.java b/src/main/java/net/marcellperger/mathexpr/util/rs/PanicException.java index fc41512..6f7f7c2 100644 --- a/src/main/java/net/marcellperger/mathexpr/util/rs/PanicException.java +++ b/src/main/java/net/marcellperger/mathexpr/util/rs/PanicException.java @@ -1,5 +1,6 @@ package net.marcellperger.mathexpr.util.rs; +@SuppressWarnings("unused") // I don't care, this is a **util** package, I'll use it later public class PanicException extends RuntimeException { public PanicException() { } diff --git a/src/main/java/net/marcellperger/mathexpr/util/rs/ResultPanicException.java b/src/main/java/net/marcellperger/mathexpr/util/rs/ResultPanicException.java index c866a1d..54f0390 100644 --- a/src/main/java/net/marcellperger/mathexpr/util/rs/ResultPanicException.java +++ b/src/main/java/net/marcellperger/mathexpr/util/rs/ResultPanicException.java @@ -47,6 +47,7 @@ public Builder cause(@Nullable Throwable cause) { } public ResultPanicException build() { + // This complication is required to ensure that initCause can be set later if not set now. return msg != null ? cause != null ? new ResultPanicException(msg, cause) : new ResultPanicException(msg) : cause != null ? new ResultPanicException(cause) : new ResultPanicException(); From 48793a4edab0f37251e5ad9df172fb67f52320a4 Mon Sep 17 00:00:00 2001 From: Marcell Perger Date: Sun, 5 May 2024 18:50:46 +0100 Subject: [PATCH 10/45] fix: Fix more IntelliJ warnings --- .idea/inspectionProfiles/Project_Default.xml | 12 ++++++++++++ .../net/marcellperger/mathexpr/parser/Parser.java | 1 + .../mathexpr/util/rs/ResultPanicException.java | 3 ++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 69f1877..e4125d4 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -6,5 +6,17 @@