Skip to content

Commit

Permalink
XLS macros extended, documented
Browse files Browse the repository at this point in the history
implemented error handling if some module has linkage error
  • Loading branch information
verhas committed May 24, 2024
1 parent 874137e commit 07276ce
Show file tree
Hide file tree
Showing 68 changed files with 1,991 additions and 357 deletions.
2 changes: 1 addition & 1 deletion README.jrf
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# This is a Jamal reference file containing serialized base64 encoded macros
# Created: 2024-05-16 22:08:30 +0200
# Created: 2024-05-24 18:56:30 +0200
# id|openStr|closeStr|verbatim|tailParameter|pure|content|parameters
# TOC
VE9D|eyU=|JX0=|0|0|0|Ci4gPDxJbnN0YWxsYXRpb24+PgouIDw8R1M+PgouIDw8Q29uZmlndXJhdGlvbj4+Ci4gPDxGZWF0dXJlcz4+Ci4gPDxDb250cmlidXRpbmc+PgouIDw8RG9jdW1lbnRhdGlvbj4+Ci4gPDxMaWNlbnNlPj4KLiA8PENoYW5nZWxvZz4+Ci4gPDxSb2FkbWFwPj4KLiA8PFN1cHBvcnQ+PgouIDw8RkFRPj4KLiA8PE1haW50ZW5hbmNlPj4=|
2 changes: 1 addition & 1 deletion documentation/ENVIRONMENT_VARIABLES.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ The default location for the cache files is `~/.jamal/cache/`.
This environment variable can define replacements for files.

The aim of this feature is to use a local file during development, and still refer to it using the `https://` URL, which will be the production URL.
You want to run tests without pushing the file to a repository, but at the same time you do not want your code to refer to a dev location to be changed before releasing.11
You want to run tests without pushing the file to a repository, but at the same time you do not want your code to refer to a dev location to be changed before releasing.

Only absolute file names can be replaced.

Expand Down
2 changes: 1 addition & 1 deletion documentation/macros/define.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ which will result:
.output
[source]
----
User defined macro '{undefinedMacro ...' is not defined.
User macro '{undefinedMacro ...' is not defined.
this is empty string: >><<
----

Expand Down
2 changes: 1 addition & 1 deletion documentation/macros/try.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ will result in
.output
[source]
----
User defined macro '{macro' is not defined. Did you want to use built-in '{@macro' instead?
User macro '{macro' is not defined. Did you mean the built-in '{@macro' instead?
hay macro
----

Expand Down
20 changes: 17 additions & 3 deletions jamal-api/src/main/java/javax0/jamal/api/BadSyntax.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package javax0.jamal.api;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static java.util.stream.Collectors.joining;
Expand Down Expand Up @@ -102,7 +103,7 @@ public interface ThrowingSupplier<T> {
*/
public static void when(final boolean condition, final ThrowingSupplier<String> message) throws BadSyntax {
if (condition) {
throw new BadSyntax(message.get());
throw castrated(new BadSyntax(message.get()));
}
}

Expand All @@ -126,10 +127,23 @@ public static void when(final boolean condition, final String format, final Obje
}

public static BadSyntax format(final Exception e, final String format, final Object... parameters) throws BadSyntax {
throw new BadSyntax(String.format(format, parameters), e);
throw castrated(new BadSyntax(String.format(format, parameters), e));
}

public static BadSyntax format(final String format, final Object... parameters) throws BadSyntax {
throw new BadSyntax(String.format(format, parameters));
throw castrated(new BadSyntax(String.format(format, parameters)));
}

/**
* Remove the elements from the stack trace that are in this class. This all belongs to the exception throwing
* and not relevant for the error itself.
*
* @param e the exception to remove the few top stack trace elements
* @return the exception with the modified stack trace
*/
private static BadSyntax castrated(final BadSyntax e) {
final var st = Arrays.stream(e.getStackTrace()).filter(s -> !s.getClassName().equals(BadSyntax.class.getName())).toArray(StackTraceElement[]::new);
e.setStackTrace(st);
return e;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ public class EnvironmentVariables {
This environment variable can define replacements for files.
The aim of this feature is to use a local file during development, and still refer to it using the `https://` URL, which will be the production URL.
You want to run tests without pushing the file to a repository, but at the same time you do not want your code to refer to a dev location to be changed before releasing.11
You want to run tests without pushing the file to a repository, but at the same time you do not want your code to refer to a dev location to be changed before releasing.
Only absolute file names can be replaced.
Expand Down
6 changes: 3 additions & 3 deletions jamal-api/src/main/java/javax0/jamal/api/Processor.java
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,8 @@ interface Logger {

/**
* @return the logger implementation that was set by the embedding application. There is no method to set the logger
* object, just as there is no metjod to set the context. Both of these objects are application specific and as the
* embedding applications are using a specific implementation of this interface they will use the one that provides
* object, just as there is no method to set the context. Both of these objects are application-specific, and as the
* embedding applications are using a specific implementation of this interface, they will use the one that provides
* the possibility to set the logger (context, {@link #getContext}).
* <p>
* The default implementation returns a null logger that just does not log.
Expand Down Expand Up @@ -361,7 +361,6 @@ public IOHookResultRedirect(final String content) {
* If the writer is set into the processor via the {@link #setFileWriter(FileWriter)} it will be invoked whenever
* a macro wants to write a file. It can be used to implement a special file system or file mapping.
*/
@FunctionalInterface
interface FileWriter {
/**
* Tries to write the file, decides on redirect or do nothing.
Expand All @@ -370,6 +369,7 @@ interface FileWriter {
* @return the structure containing the result, which is nothing, or final name
*/
IOHookResult write(final String fileName, final String content);
IOHookResult write(final String fileName, final byte[] content);
}

void setFileWriter(FileWriter fileWriter);
Expand Down
5 changes: 5 additions & 0 deletions jamal-asciidoc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
<configuration>
<argLine>
--add-opens jamal.asciidoc/javax0.jamal.asciidoc=ALL-UNNAMED

</argLine>
</configuration>
</plugin>
Expand Down Expand Up @@ -111,6 +112,10 @@
<groupId>com.javax0.jamal</groupId>
<artifactId>jamal-sql</artifactId>
</dependency>
<dependency>
<groupId>com.javax0.jamal</groupId>
<artifactId>jamal-xls</artifactId>
</dependency>
<dependency>
<groupId>com.javax0.jamal</groupId>
<artifactId>jamal-test</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,21 +86,21 @@ void testError() {
assertStartsWith(
"[WARNING]\n" +
"--\n" +
"* User defined macro '{%zebra ...' is not defined. Did you mean '@debug'? at " + TEST_INPUT + "/1:3\n" +
"* User macro '{%zebra ...' is not defined. Did you mean '@debug'? at " + TEST_INPUT + "/1:3\n" +
"--\n" +
"abrakadabra\n" +
"[WARNING]\n" +
"--\n" +
"* User defined macro '{%zebra ...' is not defined. Did you mean '@debug'? at " + TEST_INPUT + "/1:3\n" +
"* User macro '{%zebra ...' is not defined. Did you mean '@debug'? at " + TEST_INPUT + "/1:3\n" +
"--\n" +
"{%zebra%}\n" +
"[WARNING]\n" +
"--\n" +
"* User defined macro '{%zebra ...' is not defined. Did you mean '@debug'? at " + TEST_INPUT + "/1:3\n" +
"* User macro '{%zebra ...' is not defined. Did you mean '@debug'? at " + TEST_INPUT + "/1:3\n" +
"--\n" +
"[source]\n" +
"----\n" +
"User defined macro '{%zebra ...' is not defined. Did you mean '@debug'? at " + TEST_INPUT + "/1:3\n"
"User macro '{%zebra ...' is not defined. Did you mean '@debug'? at " + TEST_INPUT + "/1:3\n"
, actual);
}

Expand Down
5 changes: 5 additions & 0 deletions jamal-asciidoc258/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
<configuration>
<argLine>
--add-opens jamal.asciidoc/javax0.jamal.asciidoc=ALL-UNNAMED

</argLine>
</configuration>
</plugin>
Expand Down Expand Up @@ -92,6 +93,10 @@
<groupId>com.javax0.jamal</groupId>
<artifactId>jamal-sql</artifactId>
</dependency>
<dependency>
<groupId>com.javax0.jamal</groupId>
<artifactId>jamal-xls</artifactId>
</dependency>
<dependency>
<groupId>com.javax0.jamal</groupId>
<artifactId>jamal-test</artifactId>
Expand Down
73 changes: 44 additions & 29 deletions jamal-engine/src/main/java/javax0/jamal/engine/Processor.java
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,11 @@ public String process(final Input input) throws BadSyntax {
if (input.indexOf(macros.open()) == 0) {
skip(input, macros.open());
skipWhiteSpaces(input);
processMacro(input, output);
try {
processMacro(input, output);
}catch(LinkageError le){
throw new BadSyntax("Linkage error", le);
}
} else {
processText(input, output);
}
Expand All @@ -211,7 +215,7 @@ public String process(final Input input) throws BadSyntax {
}
processingException = badSyntax;
throw badSyntax;
} finally {
}finally {
closeProcessWithExceptionHandling(output, processingException);
}
traceRecordFactory.dump(null);
Expand Down Expand Up @@ -516,7 +520,7 @@ private String evaluateBuiltinMacro(final Input input, final Position ref, final

/**
* Evaluate a user defined macro that starts at the start of the input. If it starts with a {@code ?} character
* then the user defined macro may not be defined. In this case the result will be an empty string. Otherwise, an
* then the user defined macro may not be defined. In this case, the result will be an empty string. Otherwise, an
* undefined macro results a syntax error.<p>
*
* @param input starts at the start of the user defined macro but after the macro opening character and possibly
Expand Down Expand Up @@ -556,7 +560,7 @@ private String evalUserDefinedMacro(final Input input, final TraceRecord tr, fin
.map(ud -> (Evaluable) ud);

if (reportUndef && udMacroOpt.isEmpty()) {
throwForUndefinedUdMacro(pos, id);
throwForUndefinedUdMacro(pos, id, identifiedOpt.isPresent() && !(identifiedOpt.get() instanceof Identified.Undefined));
}
if (udMacroOpt.isPresent()) {
qualifier.udMacro = udMacroOpt.get();
Expand All @@ -578,27 +582,37 @@ private String evalUserDefinedMacro(final Input input, final TraceRecord tr, fin

/**
* Throw an exception with an error message telling that the user defined macro was not found. While creating the
* error message the code also checks if there is a built-in macro with the same name. In that case the error
* error message, the code also checks if there is a built-in macro with the same name. In that case the error
* message warns the user that probably the leading {@code #} or {@code @} was only missing.
*
* @param ref the reference to include in the exception that shows which jamal file, line and column was the error
* at
* @param id the identifier of the macro that was not found
* @throws BadSyntaxAt always, this is the main purpose of this method
* @param ref the reference to include in the exception that shows which jamal file, line and column was the error
* at
* @param id the identifier of the macro that was not found
* @param isPresent {@code true} if there is a macro with the same name, but it is not a user defined macro
* @throws BadSyntaxAt every time, this is the main purpose of this method
*/
private void throwForUndefinedUdMacro(Position ref, String id) throws BadSyntaxAt {
private void throwForUndefinedUdMacro(Position ref, String id, boolean isPresent) throws BadSyntaxAt {
final var optMacro = macros.getMacro(id);
if (optMacro.isPresent()) {
pushBadSyntax(new BadSyntax("User defined macro '" + getRegister().open() + id +
"' is not defined. Did you want to use built-in '" + getRegister().open() + "@" + id + "' instead?"), ref);
if (isPresent) {
pushBadSyntax(new BadSyntax("'" + getRegister().open() + id +
"' is defined but cannot be used as a macro. Did you mean the built-in '" + getRegister().open() + "@" + id + "' instead?"), ref);
} else {
pushBadSyntax(new BadSyntax("User macro '" + getRegister().open() + id +
"' is not defined. Did you mean the built-in '" + getRegister().open() + "@" + id + "' instead?"), ref);
}
} else {
final Set<String> suggestions = getRegister().suggest(id);
if (suggestions.isEmpty()) {
pushBadSyntax(new BadSyntax("User defined macro '" + getRegister().open() + id + " ...' is not defined."), ref);
if (isPresent) {
pushBadSyntax(new BadSyntax("'" + getRegister().open() + id + " ...' is defined but cannot be used as a macro."), ref);
} else {
pushBadSyntax(new BadSyntax("User defined macro '" + getRegister().open() + id +
" ...' is not defined. Did you mean " + suggestions.stream()
.map(s -> "'" + s + "'").collect(Collectors.joining(", ")) + "?"), ref);
final Set<String> suggestions = getRegister().suggest(id);
if (suggestions.isEmpty()) {
pushBadSyntax(new BadSyntax("User macro '" + getRegister().open() + id + " ...' is not defined."), ref);
} else {
pushBadSyntax(new BadSyntax("User macro '" + getRegister().open() + id +
" ...' is not defined. Did you mean " + suggestions.stream()
.map(s -> "'" + s + "'").collect(Collectors.joining(", ")) + "?"), ref);
}
}
}
}
Expand All @@ -607,8 +621,8 @@ private void throwForUndefinedUdMacro(Position ref, String id) throws BadSyntaxA
* Read the input of the macro and get the parameters to pass to the macro. This is a fairly complex process that
* follows several rules.
* <p>
* The method works with the partially evaluated input. If this input has no characters then the parameter array has
* zero length.
* The method works with the partially evaluated input. If this input has no characters, then the parameter array
* length is zero.
*
* @param ref the reference to the
* @param input the partially evaluated input
Expand Down Expand Up @@ -688,7 +702,7 @@ private boolean doesStartWithQuestionMark(Input input) {
}

/**
* If the start of the content itself starts with a macro then it has to be evaluated to allow constructs like
* If the start of the content itself starts with a macro, then it has to be evaluated to allow constructs like
*
* <pre>{@code
* [[@define macroName=zz]]
Expand Down Expand Up @@ -740,7 +754,7 @@ private Input evaluateMacroStart(Input input) throws BadSyntax {
}

/**
* Checks that the user defined macro name, which is the result of macro evaluation does not contain the separator
* Checks that the user defined macro name, which is the result of macro evaluation, does not contain the separator
* character.
*
* @param output the output that we check
Expand Down Expand Up @@ -770,15 +784,15 @@ private void checkEvalResultUDMacroName(Input output, Position pos) throws BadSy
* the second parameter with this string/this is the third parameter/}
* }</pre>
* <p>
* In the example above the macro will have three arguments, and it is not a problem (since version 1.2.0) that the
* In the example above, the macro will have three arguments, and it is not a problem (since version 1.2.0) that the
* second argument contains a macro that itself has parameters, and it uses the same separator character as the top
* level macro of this example. (In the example above we assumed that the macro opening and closing strings are the
* curly braces.)<p>
* <p>
* NOTE: that versions prior 1.2.0 were splitting the example above into five arguments. Although there is a
* possible use of that kind of macros this was never recommended or meant that way and because there is only a
* narrow user base of Jamal there is no backward compatibility way of operation. If you happen to face problem
* because of that then stay with version prior 1.2.0, e.g.: 1.1.0 and migrate your macros so that they do not use
* NOTE: that versions prior to 1.2.0 were splitting the example above into five arguments. Although there is a
* possible use of that kind of macro, this was never recommended or meant that way, and because there is only a
* narrow user base of Jamal, there is no backward compatibility way of operation. If you happen to face problem
* because of that, then stay with version prior 1.2.0, e.g.: 1.1.0 and migrate your macros so that they do not use
* tricks.
*
* @param in the input that starts after the first occurrence of the separator character
Expand All @@ -787,10 +801,11 @@ private void checkEvalResultUDMacroName(Input output, Position pos) throws BadSy
* are parsed as a single string.
* @return the parameter array as input, with correct positioning to where the parameters start
* @throws BadSyntaxAt if the nesting of the macro opening and closing strings do not match. The implementation does
* not check this purposefully. If there are unbalanced opening and closing strings it will not
* not check this purposefully. If there are unbalanced opening and closing strings, it will not
* be detected.
*/
private Input[] splitParameterString(final Input in, final char separator, final int expectedArgNr) throws BadSyntaxAt {
private Input[] splitParameterString(final Input in, final char separator, final int expectedArgNr) throws
BadSyntaxAt {
final var open = macros.open();
final var close = macros.close();
final var parameters = new ArrayList<Input>();
Expand Down
Loading

0 comments on commit 07276ce

Please sign in to comment.