action, final String suffix) {
- try {
- return action.act();
- } catch (final ExUnset ex) {
- throw new ExUnset(this.label(suffix), ex);
- } catch (final ExReadOnly ex) {
- throw new ExReadOnly(this.label(suffix), ex);
- } catch (final EOerror.ExError ex) {
- throw new EOerror.ExError(ex, this.label(suffix));
- } catch (final ExAbstract | Error ex) {
- throw new ExFailure(this.label(suffix), ex);
- } catch (final RuntimeException ex) {
- throw this.wrap(ex, suffix);
- }
- }
-
- /**
- * Wrap the exception into a new one.
- * @param cause Original
- * @param suffix Te suffix
- * @return New exception
- */
- private RuntimeException wrap(final RuntimeException cause,
- final String suffix) {
- final String label = this.label(suffix);
- RuntimeException ret;
- try {
- final Constructor extends RuntimeException> ctor =
- cause.getClass().getConstructor(String.class, Throwable.class);
- ret = ctor.newInstance(label, cause);
- } catch (final NoSuchMethodException | InstantiationException
- | IllegalAccessException | InvocationTargetException ex) {
- ret = new ExFailure(label, cause);
- }
- return ret;
- }
-
- /**
- * The label of the exception.
- * @param suffix The suffix to add to the label
- * @return Label
- */
- private String label(final String suffix) {
- return String.format(
- "Error in \"%s%s\" at %s:%d:%d",
- this.location, suffix, this.program, this.line, this.position
- );
- }
-}
diff --git a/eo-runtime/src/main/java/org/eolang/PhSafe.java b/eo-runtime/src/main/java/org/eolang/PhSafe.java
index bff4897426..c15c363160 100644
--- a/eo-runtime/src/main/java/org/eolang/PhSafe.java
+++ b/eo-runtime/src/main/java/org/eolang/PhSafe.java
@@ -25,75 +25,141 @@
package org.eolang;
import EOorg.EOeolang.EOerror;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
/**
- * It catches {@link ExFailure} and
- * throws {@link EOerror.ExError}.
+ * An object with coordinates (line and position) and a safe
+ * processing of any runtime errors.
*
- * @since 0.26
+ * It is used to wrap any object and provide a safe processing
+ * of any runtime errors. It is used in the EO runtime to provide
+ * a safe processing of any runtime errors in the EO code. If, in any
+ * method invocation, a runtime error occurs, it is caught and wrapped
+ * into {@link EOerror.ExError} with the location of the error in the
+ * EO code.
+ *
+ * @since 0.21
*/
@SuppressWarnings("PMD.TooManyMethods")
-public final class PhSafe implements Phi {
+public final class PhSafe implements Phi, Atom {
/**
* The original.
*/
private final Phi origin;
+ /**
+ * EO program name (the name of the {@code .eo} file).
+ */
+ private final String program;
+
+ /**
+ * The line number.
+ */
+ private final int line;
+
+ /**
+ * The position in the line.
+ */
+ private final int position;
+
+ /**
+ * The location.
+ */
+ private final String location;
+
/**
* Ctor.
+ *
* @param phi The object
+ * @checkstyle ParameterNumberCheck (5 lines)
*/
public PhSafe(final Phi phi) {
+ this(phi, "unknown", 0, 0);
+ }
+
+ /**
+ * Ctor.
+ *
+ * @param phi The object
+ * @param prg Name of the program
+ * @param lne Line
+ * @param pos Position
+ * @checkstyle ParameterNumberCheck (5 lines)
+ */
+ public PhSafe(final Phi phi, final String prg, final int lne, final int pos) {
+ this(phi, prg, lne, pos, "?");
+ }
+
+ /**
+ * Ctor.
+ *
+ * @param phi The object
+ * @param prg Name of the program
+ * @param lne Line
+ * @param pos Position
+ * @param loc Location
+ * @checkstyle ParameterNumberCheck (5 lines)
+ */
+ public PhSafe(final Phi phi, final String prg, final int lne,
+ final int pos, final String loc) {
this.origin = phi;
+ this.program = prg;
+ this.line = lne;
+ this.position = pos;
+ this.location = loc;
}
@Override
public boolean equals(final Object obj) {
- return PhSafe.through(() -> this.origin.equals(obj));
+ return this.origin.equals(obj);
}
@Override
public int hashCode() {
- return PhSafe.through(this.origin::hashCode);
+ return this.origin.hashCode();
}
@Override
public Phi copy() {
- return PhSafe.through(() -> new PhSafe(this.origin.copy()));
+ return new PhSafe(
+ this.origin.copy(), this.program,
+ this.line, this.position, this.location
+ );
}
@Override
public Phi take(final String name) {
- return PhSafe.through(() -> new PhSafe(this.origin.take(name)));
+ return this.through(() -> this.origin.take(name));
}
@Override
public boolean put(final int pos, final Phi object) {
- return PhSafe.through(() -> this.origin.put(pos, object));
+ return this.through(() -> this.origin.put(pos, object));
}
@Override
- public boolean put(final String name, final Phi object) {
- return PhSafe.through(() -> this.origin.put(name, object));
+ public boolean put(final String nme, final Phi object) {
+ return this.through(() -> this.origin.put(nme, object));
}
@Override
public String locator() {
- return PhSafe.through(this.origin::locator);
+ return String.format("%s:%d:%d", this.location, this.line, this.position);
}
@Override
public String forma() {
- return PhSafe.through(this.origin::forma);
+ return this.through(this.origin::forma);
}
@Override
public byte[] delta() {
- return PhSafe.through(this.origin::delta);
+ return this.through(this.origin::delta, ".Δ");
+ }
+
+ @Override
+ public Phi lambda() {
+ return this.through(() -> new AtomSafe((Atom) this.origin).lambda(), ".λ");
}
/**
@@ -102,53 +168,63 @@ public byte[] delta() {
* @param Type of result
* @return Result
*/
- private static T through(final Action action) {
- try {
- return action.act();
- } catch (final ExFailure ex) {
- throw new EOerror.ExError(
- new Data.ToPhi(PhSafe.message(ex)),
- PhSafe.messages(ex)
- );
- }
+ private T through(final Action action) {
+ return this.through(action, "");
}
/**
- * Make a message from an exception.
- * @param exp The exception.
- * @return Message.
+ * Helper, for other methods.
+ *
+ * No matter what happens inside the {@code action}, only
+ * an instance of {@link EOerror.ExError} may be thrown out
+ * of this method.
+ *
+ * @param action The action
+ * @param suffix The suffix to add to the label
+ * @param Type of result
+ * @return Result
+ * @checkstyle IllegalCatchCheck (20 lines)
*/
- private static String message(final Throwable exp) {
- final StringBuilder ret = new StringBuilder(0);
- if (!(exp instanceof ExFailure)) {
- ret.append(exp.getClass().getSimpleName());
- }
- if (exp.getMessage() != null) {
- if (ret.length() > 0) {
- ret.append(": ");
+ @SuppressWarnings({"PMD.AvoidCatchingThrowable", "PMD.PreserveStackTrace"})
+ private T through(final Action action, final String suffix) {
+ try {
+ return action.act();
+ } catch (final EOerror.ExError ex) {
+ throw new EOerror.ExError(ex, this.label(suffix));
+ } catch (final ExAbstract ex) {
+ throw new EOerror.ExError(
+ new Data.ToPhi(ex.getMessage()),
+ this.label(suffix)
+ );
+ } catch (final Throwable ex) {
+ final StringBuilder msg = new StringBuilder(0);
+ final StackTraceElement[] stack = ex.getStackTrace();
+ if (stack != null && stack.length > 0) {
+ final StackTraceElement last = stack[0];
+ msg.append(last.getFileName())
+ .append(':')
+ .append(last.getLineNumber())
+ .append(": ");
}
- ret.append(exp.getMessage().replace("%", "%%"));
- }
- if (exp.getCause() != null) {
- ret.setLength(0);
- ret.append(PhSafe.message(exp.getCause()));
+ msg.append(ex.getClass().getSimpleName())
+ .append(": ")
+ .append(ex.getMessage());
+ throw new EOerror.ExError(
+ new Data.ToPhi(msg.toString()),
+ this.label(suffix)
+ );
}
- return ret.toString();
}
/**
- * Make a chain of messages from an exception and its causes.
- * @param exp The exception
- * @return Messages
+ * The label of the exception.
+ * @param suffix The suffix to add to the label
+ * @return Label
*/
- private static List messages(final Throwable exp) {
- final List msgs = new LinkedList<>();
- if (exp != null) {
- msgs.add(exp.getMessage());
- msgs.addAll(PhSafe.messages(exp.getCause()));
- Collections.reverse(msgs);
- }
- return msgs;
+ private String label(final String suffix) {
+ return String.format(
+ "Error in \"%s%s\" at %s:%d:%d",
+ this.location, suffix, this.program, this.line, this.position
+ );
}
-
}
diff --git a/eo-runtime/src/test/java/EOorg/EOeolang/EObytesEOsliceTest.java b/eo-runtime/src/test/java/EOorg/EOeolang/EObytesEOsliceTest.java
index 3e1d6a9731..2def3adcf0 100644
--- a/eo-runtime/src/test/java/EOorg/EOeolang/EObytesEOsliceTest.java
+++ b/eo-runtime/src/test/java/EOorg/EOeolang/EObytesEOsliceTest.java
@@ -32,7 +32,6 @@
import java.io.PrintWriter;
import org.eolang.Data;
import org.eolang.Dataized;
-import org.eolang.ExFailure;
import org.eolang.PhWith;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
@@ -71,8 +70,8 @@ void takesLegalSlice() {
@Test
void takesWrongSlice() {
- final ExFailure exp = Assertions.assertThrows(
- ExFailure.class,
+ final EOerror.ExError exp = Assertions.assertThrows(
+ EOerror.ExError.class,
() -> new Dataized(
new PhWith(
new PhWith(
@@ -96,10 +95,7 @@ void takesWrongSlice() {
MatcherAssert.assertThat(
"error message is correct",
baos.toString(),
- Matchers.allOf(
- Matchers.containsString("the 'len' attribute must be a positive integer"),
- Matchers.containsString("the 'len' attribute (-5)")
- )
+ Matchers.containsString("the 'len' attribute (-5) must be a positive integer")
);
}
diff --git a/eo-runtime/src/test/java/EOorg/EOeolang/EOtryTest.java b/eo-runtime/src/test/java/EOorg/EOeolang/EOtryTest.java
index 6625acbe7d..02fc3562a7 100644
--- a/eo-runtime/src/test/java/EOorg/EOeolang/EOtryTest.java
+++ b/eo-runtime/src/test/java/EOorg/EOeolang/EOtryTest.java
@@ -53,7 +53,7 @@ final class EOtryTest {
@Test
void catchesException() {
MatcherAssert.assertThat(
- AtCompositeTest.TO_ADD_MESSAGE,
+ "catches exception",
new Dataized(
new PhWith(
new PhWith(
@@ -73,20 +73,21 @@ void catchesException() {
@Test
void usesCatcherOutput() {
- final Phi body = new PhWith(
- new PhWith(
- new PhWith(
- new EOtry(),
- 0, new PhSafe(new Broken())
- ),
- 1, new Catcher()
- ),
- 2,
- new Data.ToPhi(true)
- );
MatcherAssert.assertThat(
- AtCompositeTest.TO_ADD_MESSAGE,
- new Dataized(body).asString(),
+ "uses catcher's output",
+ new Dataized(
+ new PhWith(
+ new PhWith(
+ new PhWith(
+ new EOtry(),
+ 0, new PhSafe(new Broken())
+ ),
+ 1, new Catcher()
+ ),
+ 2,
+ new Data.ToPhi(true)
+ )
+ ).asString(),
Matchers.containsString("it is broken")
);
}
diff --git a/eo-runtime/src/test/java/org/eolang/DataizedTest.java b/eo-runtime/src/test/java/org/eolang/DataizedTest.java
index 827d7339a5..a6e4a101df 100644
--- a/eo-runtime/src/test/java/org/eolang/DataizedTest.java
+++ b/eo-runtime/src/test/java/org/eolang/DataizedTest.java
@@ -56,11 +56,20 @@ void logsAllLocationsWithPhSafe() {
EOerror.ExError.class,
() -> new Dataized(
new PhSafe(
- new PhLocated(new PhIncorrect(), "foo.bar", 0, 0)
+ new PhMethod(
+ new PhDefault() {
+ @Override
+ public Phi take(final String name) {
+ throw new IllegalStateException("intentional error");
+ }
+ },
+ "xyz"
+ ),
+ "foo.bar", 0, 0
),
log
).take(),
- "it is expected to fail with ExFailure exception"
+ "it is expected to fail with and exception"
);
log.setLevel(before);
log.removeHandler(hnd);
@@ -69,10 +78,8 @@ void logsAllLocationsWithPhSafe() {
logs.get(0).getMessage(),
Matchers.allOf(
Matchers.containsString("1) Error in"),
- Matchers.containsString("2) There's no"),
- Matchers.containsString("3)"),
Matchers.containsString("at foo.bar:0:0"),
- Matchers.containsString("no \"Δ\" in the object of")
+ Matchers.containsString("intentional error")
)
);
}
@@ -92,55 +99,6 @@ void failsWhenError() {
);
}
- /**
- * Fake Phi failing when dataized.
- * @since 0.1.0
- */
- private static class PhIncorrect extends PhDefault {
-
- /**
- * Ctor.
- */
- @SuppressWarnings("PMD.ConstructorOnlyInitializesOrCallOtherConstructors")
- PhIncorrect() {
- this.add(
- "Δ",
- new AtComposite(
- this,
- rho -> Phi.Φ
- )
- );
- }
- }
-
- /**
- * Fake Phi with decoration.
- *
- * @since 0.1.0
- */
- public static class PhiDec extends PhDefault {
-
- /**
- * Ctor.
- */
- @SuppressWarnings("PMD.ConstructorOnlyInitializesOrCallOtherConstructors")
- PhiDec() {
- this.add(
- "φ",
- new AtOnce(
- new AtComposite(
- this,
- rho -> new PhWith(
- new PhCopy(new PhMethod(new Data.ToPhi(2L), "plus")),
- 0,
- new Data.ToPhi(2L)
- )
- )
- )
- );
- }
- }
-
/**
* Handler implementation for tests.
*
diff --git a/eo-runtime/src/test/java/org/eolang/PhLocatedTest.java b/eo-runtime/src/test/java/org/eolang/PhLocatedTest.java
deleted file mode 100644
index 3db7275b4b..0000000000
--- a/eo-runtime/src/test/java/org/eolang/PhLocatedTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * The MIT License (MIT)
- *
- * Copyright (c) 2016-2024 Objectionary.com
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included
- * in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-package org.eolang;
-
-import org.hamcrest.MatcherAssert;
-import org.hamcrest.Matchers;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Test;
-
-/**
- * Test case for {@link PhLocatedTest}.
- *
- * @since 0.36.0
- */
-final class PhLocatedTest {
-
- @Test
- void savesLocationAfterCopying() {
- final Phi located = new PhLocated(new Data.ToPhi(0L), "foo", 123, 124, "qwerty");
- MatcherAssert.assertThat(
- "saves location",
- located.copy().locator(),
- Matchers.equalTo(located.locator())
- );
- }
-
- @Test
- void catchesRuntimeException() {
- MatcherAssert.assertThat(
- "rethrows correctly",
- Assertions.assertThrows(
- IllegalArgumentException.class,
- () -> new PhLocated(
- new PhDefault() {
- @Override
- public byte[] delta() {
- throw new IllegalArgumentException("oops");
- }
- },
- "foobar", 10, 20
- ).delta(),
- "throws correct class"
- ),
- Matchers.hasToString(
- Matchers.containsString("Error in \"?.Δ\" at foobar:10:20")
- )
- );
- }
-}
diff --git a/eo-runtime/src/test/java/org/eolang/PhSafeTest.java b/eo-runtime/src/test/java/org/eolang/PhSafeTest.java
index 903ae44d34..e8360630c4 100644
--- a/eo-runtime/src/test/java/org/eolang/PhSafeTest.java
+++ b/eo-runtime/src/test/java/org/eolang/PhSafeTest.java
@@ -30,12 +30,44 @@
import org.junit.jupiter.api.Test;
/**
- * Test case for {@link PhSafe}.
+ * Test case for {@link PhSafeTest}.
*
- * @since 0.42
+ * @since 0.36.0
*/
final class PhSafeTest {
+ @Test
+ void savesLocationAfterCopying() {
+ final Phi located = new PhSafe(new Data.ToPhi(0L), "foo", 123, 124, "qwerty");
+ MatcherAssert.assertThat(
+ "saves location",
+ located.copy().locator(),
+ Matchers.equalTo(located.locator())
+ );
+ }
+
+ @Test
+ void catchesRuntimeException() {
+ MatcherAssert.assertThat(
+ "rethrows correctly",
+ Assertions.assertThrows(
+ EOerror.ExError.class,
+ () -> new PhSafe(
+ new PhDefault() {
+ @Override
+ public byte[] delta() {
+ throw new IllegalArgumentException("oops");
+ }
+ }
+ ).delta(),
+ "throws correct class"
+ ).messages(),
+ Matchers.hasItem(
+ Matchers.containsString("Error in \"?.Δ\" at unknown:0:0")
+ )
+ );
+ }
+
@Test
void rendersMultiLayeredErrorMessageCorrectly() {
MatcherAssert.assertThat(
@@ -57,4 +89,30 @@ void rendersMultiLayeredErrorMessageCorrectly() {
);
}
+ @Test
+ void showsFileNameAndLineNumber() {
+ MatcherAssert.assertThat(
+ "shows file name and line number",
+ new Dataized(
+ Assertions.assertThrows(
+ EOerror.ExError.class,
+ () -> new PhSafe(
+ new PhDefault() {
+ @Override
+ public Phi take(final String name) {
+ throw new IllegalArgumentException("intentional error");
+ }
+ }
+ ).take("foo"),
+ "throws correct class"
+ ).enclosure()
+ ).take(String.class),
+ Matchers.allOf(
+ Matchers.startsWith("PhSafeTest.java:"),
+ Matchers.containsString("IllegalArgumentException"),
+ Matchers.containsString("intentional error")
+ )
+ );
+ }
+
}
diff --git a/eo-runtime/src/test/java/org/eolang/PhiTest.java b/eo-runtime/src/test/java/org/eolang/PhiTest.java
index 6761f4dbc7..ca5aa723f9 100644
--- a/eo-runtime/src/test/java/org/eolang/PhiTest.java
+++ b/eo-runtime/src/test/java/org/eolang/PhiTest.java
@@ -99,7 +99,7 @@ void takesDirectly() {
void getsLocation() {
MatcherAssert.assertThat(
AtCompositeTest.TO_ADD_MESSAGE,
- new PhLocated(
+ new PhSafe(
Phi.Φ,
"foobar",
123,