Skip to content

Commit 607e6fe

Browse files
committed
some refactoring deferred error handling in the processor
1 parent 7c9eb6c commit 607e6fe

100 files changed

Lines changed: 829 additions & 829 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.mvn/touch

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
000
1+
1740913148716

README.jrf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# This is a Jamal reference file containing serialized base64 encoded macros
2-
# Created: 2025-02-27 17:32:21 +0100
2+
# Created: 2025-03-02 11:57:09 +0100
33
# id|openStr|closeStr|verbatim|tailParameter|pure|content|parameters
44
# TOC
55
VE9D|eyU=|JX0=|0|0|0|Ci4gPDxJbnN0YWxsYXRpb24+PgouIDw8R1M+PgouIDw8Q29uZmlndXJhdGlvbj4+Ci4gPDxGZWF0dXJlcz4+Ci4gPDxDb250cmlidXRpbmc+PgouIDw8RG9jdW1lbnRhdGlvbj4+Ci4gPDxMaWNlbnNlPj4KLiA8PENoYW5nZWxvZz4+Ci4gPDxSb2FkbWFwPj4KLiA8PFN1cHBvcnQ+PgouIDw8RkFRPj4KLiA8PE1haW50ZW5hbmNlPj4=|

jamal-api/src/main/java/javax0/jamal/api/BadSyntax.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ public String getMessage() {
139139
public interface ThrowingSupplier<T> {
140140
T get() throws BadSyntax;
141141
}
142+
public interface ThrowingRunnable {
143+
void run() throws BadSyntax;
144+
}
142145

143146
/**
144147
* This method throws a {@code BadSyntax} exception when the {@code condition} is {@code true}.

jamal-api/src/main/java/javax0/jamal/api/Macro.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,14 @@ interface Escape {
4242

4343
/**
4444
* A signal interface to be implemented by all macros that implement state.
45-
* Implementing state, a.k.a. having a non-static non-final field, is not recommended, but it is possible.
45+
* Implementing state, a.k.a. having a non-static non-final field is possible.
4646
* When a macro is stateful then it has to be annotated using {@link Macro.Stateful}.
4747
* <p>
4848
* The use of this macro helps to ensure that the developer is aware of the fact that the macro is stateful.
49+
* <p>
50+
* The macro loading implementation uses a separate service loader for each processor instance, therefore, it
51+
* is guaranteed that the macro object is not shared between processors and hence the state os tied to the
52+
* current processor.
4953
*/
5054
@Target(java.lang.annotation.ElementType.TYPE)
5155
@Retention(RetentionPolicy.RUNTIME)

jamal-api/src/main/java/javax0/jamal/api/Processor.java

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ default UserDefinedMacro newUserDefinedMacro(String id, String input, boolean ve
148148
/**
149149
* Register an AutoCloseable closer that has to be closed when the execution is finished.
150150
* <p>
151-
* Some user defined (Java implemented) or built-in macro may create resources that perform some actions
151+
* Some user-defined (Java implemented) or built-in macro may create resources that perform some actions
152152
* asynchronous. The typical example is when a macro that creates some external resource starts a separate thread to
153153
* execute the task. This task has to be joined at the end of the processing. The general model is that there is a
154154
* resource that has to be closed. The {@code closer} may be the resource itself or some object that will close the
@@ -181,7 +181,7 @@ default UserDefinedMacro newUserDefinedMacro(String id, String input, boolean ve
181181
* the input is processed, the invocation of the closers registered in the first round continues. Any closer
182182
* registered during the call to {@link Processor#process(Input) process(Input)} from a closer will be ignored.
183183
* <p>
184-
* Calling this method the macro can register an {@link AutoCloseable} object. The method {@link
184+
* Calling this method, the macro can register an {@link AutoCloseable} object. The method {@link
185185
* AutoCloseable#close() close()} will be invoked when the method {@link Processor#process(Input)} finishes its top
186186
* level execution. When the method is called in recursive calls from a macro or from any other place the deferred
187187
* resources will not be closed upon return, only when the top level call is to be returned.
@@ -191,6 +191,9 @@ default UserDefinedMacro newUserDefinedMacro(String id, String input, boolean ve
191191
* once. In the order of executions, the first registering is relevant. A closer {@code c2} is treated as already
192192
* registered if there is a registered closer {@code c1} so that {@code c1.equals(c2)}.
193193
* <p>
194+
* It is important that the closer implemented {@link Object#equals(Object) equals(Object other)} never returns
195+
* {@code true} if the {@code other} object is not an instance of the same class.
196+
* <p>
194197
* It also means that any call to this method must use the return value of the method to reference the closer and
195198
* not the object passed as argument, unless they are the same object. If you pass an object {@code c2} that is
196199
* {@code c1.equals(c2)} but are not the exact same, then the method will return {@code c1} and the object {@code
@@ -199,19 +202,56 @@ default UserDefinedMacro newUserDefinedMacro(String id, String input, boolean ve
199202
* This approach was created to allow the macros to create cheap closer objects and call them from the macro
200203
* evaluation. In this case, the macro can register the closer when it knows that a closer is needed, and it does
201204
* not need to maintain a state and remember if there was already a closer registered. Also, the closer is not
202-
* directly associated with the macro making it possible to register multiple closers assuming they are not
205+
* directly associated with the macro, making it possible to register multiple closers assuming they are not
203206
* equal to each other.
204207
* <p>
205208
* Note that this method, or any other method of the processor MUST NOT be invoked from other than the main thread
206-
* of the Jamal processing. Even if a macro spawns a new thread the new thread must not do anything with the
209+
* of the Jamal processing. Even if a macro spawns a new thread, the new thread must not do anything with the
207210
* processor.
211+
* <p>
212+
* The closer has to implement {@link AutoCloseable} and not {@link java.io.Closeable} because
213+
* {@link java.io.Closeable} does not allow
214+
* checked exceptions, like BadSyntax from the method {@link AutoCloseable#close() close()}.
208215
*
209-
* @param closer the autocloseable object to be closed at the end of the processing.
216+
* @param closer the AutoCloseable object to be closed at the end of the processing.
210217
* @return the registered closer. It may not be the same closer as the argument {@code closer}. If the closer
211218
* was already registered, then the first registered closer will be returned. More formally, if there was a {@code
212219
* closer2} already registered such that {@code closer2.equals(closer)} then {@code closer2} will be returned.
213220
*/
214-
AutoCloseable deferredClose(AutoCloseable closer);
221+
<T extends AutoCloseable> T deferredClose(T closer);
222+
223+
/**
224+
* Create a bad syntax exception and add it to the list of exceptions to be thrown at the end of the processing.
225+
* <p>
226+
* This is a convenience method proxying {@link #deferredThrow(BadSyntax)}.
227+
*
228+
* @param errorMessage is the error message used in String format
229+
* @param parameters the parameters to create the error message
230+
*/
231+
void deferredThrow(final String errorMessage, Object... parameters);
232+
233+
/**
234+
* Add the BadSyntax exception to the list of exceptions to be thrown at the end of the processing.
235+
* <p>
236+
* Call this method instead of throwing a BadSyntax exception whenever further processing of the input is
237+
* possible and can be advantageous to discover further syntax errors. That way the user can fix multiple
238+
* syntax errors without having to rerun the program with the fixed first error, then the second and so on.
239+
* <p>
240+
* It is recommended to call {@link #newUserDefinedMacro(String, String, String...)} unless there is a special
241+
* need to create the exception on the caller side, for example, adding a cause to the exception.
242+
*
243+
*
244+
* @param bs the BadSyntax to be thrown
245+
*/
246+
void deferredThrow(final BadSyntax bs);
247+
248+
default void deferBadSyntax(BadSyntax.ThrowingRunnable runner){
249+
try{
250+
runner.run();
251+
}catch(BadSyntax bs){
252+
deferredThrow(bs);
253+
}
254+
}
215255

216256
/**
217257
* Get the context object that the embedding application was setting. The context object is a general object and the
@@ -369,6 +409,7 @@ interface FileWriter {
369409
* @return the structure containing the result, which is nothing, or final name
370410
*/
371411
IOHookResult write(final String fileName, final String content);
412+
372413
IOHookResult write(final String fileName, final byte[] content);
373414
}
374415

jamal-api/src/main/java/javax0/jamal/api/ServiceLoaded.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,31 +22,31 @@
2222
public interface ServiceLoaded {
2323

2424
/**
25-
* Load the classes that implement the interface {@code klass} and are provided by the modules or are available.
25+
* Load the classes that implement the interface {@code service} and are provided by the modules or are available.
2626
*
27-
* @param klass the interface for which the implementing class instances are needed
27+
* @param service the interface for which the implementing class instances are needed
2828
* @param <T> the interface
2929
* @return the list of instances
3030
*/
31-
static <T> List<T> getInstances(Class<T> klass) {
32-
final var services = getInstances(klass, Thread.currentThread().getContextClassLoader());
31+
static <T> List<T> getInstances(Class<T> service) {
32+
final var services = getInstances(service, Thread.currentThread().getContextClassLoader());
3333
if(!services.isEmpty()){
3434
return services;
3535
}
36-
return getInstances(klass, ServiceLoaded.class.getClassLoader());
36+
return getInstances(service, ServiceLoaded.class.getClassLoader());
3737
}
3838

39-
static <T> List<T> getInstances(Class<T> klass, final ClassLoader cl) {
39+
static <T> List<T> getInstances(Class<T> service, final ClassLoader cl) {
4040
List<T> list = new ArrayList<>();
4141
try {
42-
final ServiceLoader<T> services = ServiceLoader.load(klass, cl);
42+
final ServiceLoader<T> services = ServiceLoader.load(service, cl);
4343
services.iterator().forEachRemaining(list::add);
4444
if (list.isEmpty()) {
45-
loadViaMetaInf(klass, list, cl);
45+
loadViaMetaInf(service, list, cl);
4646
}
4747
return list;
4848
} catch (ServiceConfigurationError ignored) {
49-
loadViaMetaInf(klass, list, cl);
49+
loadViaMetaInf(service, list, cl);
5050
return list;
5151
}
5252
}

jamal-asciidoc/src/main/java/javax0/jamal/asciidoc/ExceptionDumper.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ private void dumpIt(Throwable t, boolean printMessage) {
4343
output.append(t.getMessage());
4444
}
4545
for (final var s : t.getStackTrace()) {
46+
if (s.getClassName().startsWith("javax0.jamal.engine")) {
47+
output.append("\n");
48+
break; // we do not need to dump the engine trace or above embedding
49+
}
4650
if (s.getClassName().startsWith("javax0.jamal")) {
4751
output.append(String.format("\t%s(%s:%d)\n", s.getClassName(), s.getMethodName(), s.getLineNumber()));
4852
}

jamal-core/src/main/java/javax0/jamal/builtins/Catch.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import javax0.jamal.tools.Option;
99
import javax0.jamal.tools.OptionsStore;
1010

11+
@Macro.Name("catch")
1112
public class Catch implements Macro {
1213
@Override
1314
public String evaluate(Input in, Processor processor) throws BadSyntax {
@@ -22,10 +23,6 @@ public String evaluate(Input in, Processor processor) throws BadSyntax {
2223
}
2324
}
2425

25-
@Override
26-
public String getId() {
27-
return "catch";
28-
}
2926
}
3027
/*template jm_catch
3128
{template |catch|catch $C$|catch an error|

jamal-engine/src/main/java/javax0/jamal/engine/Processor.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public class Processor implements javax0.jamal.api.Processor {
4646
final private JShellEngine shellEngine = getEngine();
4747
// cannot be a set, you cannot easily retrieve the already stored value when you give a new closer 'equals' the existing
4848
final private Map<AutoCloseable, AutoCloseable> openResources = new LinkedHashMap<>();
49+
final private List<BadSyntax> deferredExceptions = new ArrayList<>();
4950
private final Context context;
5051
private final Debugger debugger;
5152
private final DebuggerStub debuggerStub = new DebuggerStub(this);
@@ -223,6 +224,16 @@ public String process(final Input input) throws BadSyntax {
223224
return output.toString();
224225
}
225226

227+
@Override
228+
public void deferredThrow(final String errorMessage, final Object... parameters) {
229+
deferredThrow(new BadSyntax(String.format(errorMessage, parameters)));
230+
}
231+
232+
@Override
233+
public void deferredThrow(final BadSyntax bs) {
234+
deferredExceptions.add(bs);
235+
}
236+
226237
/**
227238
* Handles the closing process of the processor with exception handling.
228239
* If an exception occurs during the closing process, it is thrown after adding a possibly existing processing
@@ -235,7 +246,13 @@ public String process(final Input input) throws BadSyntax {
235246
private void closeProcessWithExceptionHandling(javax0.jamal.tools.Input output, BadSyntax processingException) throws BadSyntax {
236247
try {
237248
if (limiter.down() == 0) {
238-
closeProcess(output);
249+
if (deferredExceptions.isEmpty()) {
250+
closeProcess(output);
251+
} else {
252+
final var bs = new BadSyntax(String.format("There were %d syntax error(s)", deferredExceptions.size()));
253+
deferredExceptions.forEach(bs::addSuppressed);
254+
throw bs;
255+
}
239256
}
240257
} catch (Exception e) {
241258
if (processingException != null) {
@@ -443,7 +460,7 @@ private String evalMacro(final TraceRecord tr, final MacroQualifier qualifier, R
443460
} catch (BadSyntax bs) {
444461
throw new BadSyntaxAt(bs, ref);
445462
} finally {
446-
if( segmentsSize < rf.segment.size() ) {
463+
if (segmentsSize < rf.segment.size()) {
447464
rf.popSegment();
448465
}
449466
}
@@ -1042,11 +1059,12 @@ public Context getContext() {
10421059
}
10431060

10441061
@Override
1045-
public AutoCloseable deferredClose(AutoCloseable closer) {
1062+
public <T extends AutoCloseable> T deferredClose(T closer) {
10461063
if (!openResources.containsKey(closer)) {
10471064
openResources.put(closer, closer);
10481065
}
1049-
return openResources.get(closer);
1066+
//noinspection unchecked
1067+
return (T) openResources.get(closer);
10501068
}
10511069

10521070
private static JShellEngine getEngine() {

jamal-extensions/src/main/java/javax0/jamal/extensions/IndexStringTable.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
* <pre>
3131
* {{#get 1 2 |a/b/c|h/k/j|o/z}}
3232
* </pre>
33-
*
33+
* <p>
3434
* will result {@code j} because that is the third element of the second
3535
* table. (Indexing starts with zero.)
3636
*
@@ -56,7 +56,9 @@
5656
* as a separator character on the top level, the second on the next and
5757
* so on.
5858
*/
59-
public class IndexStringTable implements Macro {
59+
@Macro.Name("get")
60+
public
61+
class IndexStringTable implements Macro {
6062
private static final String DEFAULT = "|/:-.";
6163

6264
@Override
@@ -77,7 +79,7 @@ public String evaluate(Input in, Processor processor) throws BadSyntax {
7779
BadSyntax.when(splitters.length() == 0, "$getsep defines a zero length separator");
7880
final var input = in.toString();
7981
final var index = input.indexOf(splitters.charAt(0));
80-
BadSyntax.when(index == -1, "There is no separator '%s' in the input.", splitters.charAt(0));
82+
BadSyntax.when(index == -1, "There is no separator '%s' in the input.", splitters.charAt(0));
8183
final var indices = input.substring(0, index);
8284
var table = input.substring(index + 1);
8385
final var indexArray = Arrays.stream(indices.trim().split("\\s+")).mapToInt(Integer::parseInt).map(Math::abs).toArray();
@@ -88,14 +90,10 @@ public String evaluate(Input in, Processor processor) throws BadSyntax {
8890
final var table_ = table;
8991
final var i_ = i;
9092
BadSyntax.when(indexArray[i] >= cols.length, () -> String.format("There are only %d columns in the string \"%s\" using the splitter %s and macro 'get' wants to access %d",
91-
cols.length, table_, splitter, indexArray[i_]));
93+
cols.length, table_, splitter, indexArray[i_]));
9294
table = cols[indexArray[i]];
9395
}
9496
return table;
9597
}
9698

97-
@Override
98-
public String getId() {
99-
return "get";
100-
}
10199
}

0 commit comments

Comments
 (0)