Jamal is a macro language (JAmal MAcro Language)

Overview
logo

Jamal Macro Language

jamal parent

Java CI with Maven

Jamal is a complex text processor with a wide variety of possible use. The first version of Jamal was developed 20 years ago in Perl. It was used in many projects to simplify build-scripts maintenance, content text, and many other things. The old Perl version is still in use for the build chain maintenance of the ScriptBasic scripting language, which is used in many industrial products. This version of Jamal is a complete rewrite of the original processor in Java. Jamal leverages on the programming language’s features, and the twenty years of experience of using the old version. In this documentation, the term "Jamal" refers to the Java implementation of the macro language.

text2text

Jamal’s basic concept is to transform a source text to a target text enabling programmatic constructs in the source text. That way, it enables the maintainer of the text to

  • eliminate repetitions, calculable text, and other redundancies

  • ensure internal consistency, and

  • support consistency with the documented external system(s).

Jamal design is to be transparent and applicable to any target text file and format. It uses special start and stop strings for the macros and all other text is coped to the output. The start and stop string are not hardwired, can be configured and even dynamically changed in the input. Jamal can create your file from a Jamal source file no matter any particular need to keep new lines, spacing, or special characters. That way, Jamal can be applied as a universal, programmable preprocessor to any text file type. The types include document formats, like Markdown, AsciiDoc, HTML, and textual data description formats, like JSON, YAML, XML, or even programming languages.

The source text

  • uses built-in macros (there is a purposefully limited number of them in the core module and there are a lot in different other modules),

  • define and use previously defined user-defined macros,

  • include files,

  • use custom-made macros,

  • include and execute JShell, ScriptBasic, Groovy, Ruby or other scripts,

  • works with yaml, XML data,

  • import documentation and code snippets from the documented application or over the network,

  • can create PlantUML diagrams, and

  • can set assertions checking the consistency of the documentation with itself and with the documented system.

You can start Jamal processing

  • on the command line even without installing Jamal itself automatically downloading from the net,

  • as a Maven plugin,

  • starting with jbang,

  • as a Maven extension letting you use Jamal macros in your POM files,

  • as a JavaDoc doclet letting you use Jamal macros in your JavaDoc documentation (including markdown), and

  • embedded into applications using a simple API, for example the Yamaledt JUnit 5 YAML test data source library.

You can extend the set of built-in macros creating new macros in any JVM language.

Jamal is a text to text processor. It is also a templating engine to maintain redundant text files. During development, there are often text files with redundant information you may need to maintain.

  • Some property or other resource files in a Java project may be slightly different for the different environments, test support, user acceptance test, production. Changing something in one of them many times should be followed in the other versions. This can be automated using Jamal.

  • A textual documentation has cross-references, but the format does not support automatic symbolic anchors and references. Jamal macros can keep track of the references and give you a warning when a reference becomes obsolete needing update.

  • The documentation may refer to some files in the project or some Java objects. Jamal macros can check that the files, classes, methods or fields exist and in case they were moved, removed, renamed Jamal will warn you to update the documentation.

  • You can use Jamal to automatically copy some part of your code source files as examples into the documentation. Whenever the source changes the output will automatically change when Jamal runs.

  • Jamal can collect and transform documentation text form different sources. You can put some of the documentation into the source code right at the place where the functionality is implemented. You have less chance forgetting the documentation update related to that feature whenever it changes. Jamal will fetch the documentation from the file, transform it to fit the output format and generate up-to-date compiled documentation.

  • You can embed Plant UML diagram text definition into your documentation source. The Plant UML macro extension will generate the diagrams and create the image reference into the output document. Some document format support embedding Plant UML. Using Jamal you can embed Plant UML into any textual documentation format. Because the diagram description is part of a Jamal macro processed source you can use Jamal macros in the diagrams. That way you are not limited by the macro features supported by Plant UML.

  • If you have any other use, please tell us.

Generally, Jamal reads a text and generates another one. In the source file, it processes macros, and the result gets in the macros' place. That way, text, and macros are mixed conveniently.

You can use Jamal as a maven plugin. The Java::Geci code generators also support Jamal, where you can write your Java code template using Jamal. You can use it as an embeddable macro engine in your Java application. Jamal can be used in JavaDoc using the Jamal Doclet implementation.

These different features are documented in their respective module documentations:

Applications, Embedding

Programming Language Modules

Other External Modules

Test Support

In this readme, we first discuss how the macros look and how Jamal will convert its input to the output. Then we discuss the API that lets you embed the macro processing into your application.

Table of contents

1. Starting Jamal

JShell

The simplest way to start Jamal is to use the JShell.

All you need to do is execute the following command:

jshell https://git.io/jamal
Note
The URL is a shortened URL of GitHub. It redirects to https://raw.githubusercontent.com/verhas/jamal/master/jamal-cmd/jamal.jshell

It will start Jamal to process all files with .jam extension in the current directory and below. The output files will have the same name as the processed file without the .jam at the end. For example, pom.xml.jam will be processed to pom.xml.

You do not even need to install Jamal. If you have Java 11 or later installed, you can execute the above command. JShell will download and execute the script from the URL depicted above. The script will check if Jamal is installed on your machine. If it is not installed, it will automatically download the needed JAR. When the JAR files are downloaded, it will start Jamal in the current working directory using the default settings. You can alter the settings using the jamal.options file. If this file does not exist in the current working directory, then the JShell script will create one containing the default settings.

Maven Plugin

It is also straightforward to start Jamal using the Maven plugin version. To do that, you have to have Maven installed, but as a Java developer, you probably have. Then you can issue the command:

mvn com.javax0.jamal:jamal-maven-plugin:1.0.2:jamal

if you have a pom.xml file in your directory.

If you do not have, then read the documentation of the Jamal Maven plugin at https://github.com/verhas/jamal/blob/master/jamal-maven-plugin/README.md It is short and straightforward.

When something goes wrong, then Jamal will give you a detailed error message. The message will include the file name, line number, and character count where the error happened. Jamal may think it works fine in other cases, but the output is not exactly what you expected. Sorry, in this case, the issue, most probably, is with your expectations. Jamal converts the text following the rules defined in this document.

Maven Extension

If you want to use Jamal macros to maintain your Maven POM files, you can do that. Edit the content of the POM XML in the file pom.xml.jam. This file should contain the POM XML possibly enhanced with Jamal macros. Create a .mvn directory with an extensions.xml file in your project root. About the content and how this Maven extension works read the extension’s documentation.

Starting Command Line Version

To start Jamal on the command line, you need a command:

java -cp $HOME/.m2/repository/com/javax0/jamal/jamal-engine/1.10.4/jamal-engine-1.10.4.jar:$HOME/.m2/repository/com/javax0/jamal/jamal-api/1.10.4/jamal-api-1.10.4.jar:$HOME/.m2/repository/com/javax0/jamal/jamal-tools/1.10.4/jamal-tools-1.10.4.jar:$HOME/.m2/repository/com/javax0/jamal/jamal-core/1.10.4/jamal-core-1.10.4.jar:$HOME/.m2/repository/com/javax0/jamal/jamal-cmd/1.10.4/jamal-cmd-1.10.4.jar: javax0.jamal.cmd.JamalMain options

It is not a user-friendly approach. You do not want to type all the paths, and the JARs every time you want to start Jamal. For this reason, there is a file, jamal.sh that has the following content:

#!/usr/bin/env bash
MODULES="api engine core tools cmd"
REPO=$HOME/.m2/repository/com/javax0/jamal
VERSION=1.10.4

for MODULE in $MODULES ; do
  if ! test -f $REPO/jamal-$MODULE/$VERSION/jamal-$MODULE-$VERSION.jar; then
    if command -v wget &>/dev/null; then
      wget --no-check-certificate https://repo1.maven.org/maven2/com/javax0/jamal/jamal-$MODULE/$VERSION/jamal-$MODULE-$VERSION.jar -O $REPO/jamal-$MODULE/$VERSION/jamal-$MODULE-$VERSION.jar
    else
      if command -v curl &>/dev/null; then
        curl https://repo1.maven.org/maven2/com/javax0/jamal/jamal-$MODULE/$VERSION/jamal-$MODULE-$VERSION.jar -o $REPO/jamal-$MODULE/$VERSION/jamal-$MODULE-$VERSION.jar
      else
        echo "There is no curl nor wget available"
        exit -1
      fi
    fi
  fi
done

CLASSPATH=""
for MODULE in $MODULES ; do
  CLASSPATH=$REPO/jamal-$MODULE/$VERSION/jamal-$MODULE-$VERSION.jar:$CLASSPATH
done

java -cp $CLASSPATH javax0.jamal.cmd.JamalMain $*

The shell variable MODULES should list the Jamal modules you may need to use in your processing. The basic modules needed under every circumstance are listed in the example. The other modules available are snippet, scriptbasic, groovy, ruby, plantuml, and debug. The shell variable REPO must be set to point to the repository where your local JAR files are. VERSION has to be the latest version or the one you intend to use.

The invocation of the shell script is ./jamal.sh options where the options have the key=value format. If you use a simple option help, then Jamal will print out a short screen that looks something like this:

Usage: <main class> [-fhvx] [--dry-dry-run] [--dry-run] [-c=<macroClose>]
                    [-d=<depth>] [-e=<exclude>] [-g=<debug>] [-i=<include>]
                    [-o=<macroOpen>] [-s=<sourceDirectory>]
                    [-t=<targetDirectory>] [-r=<transform> [<transform>]]...
                    [<inputFile>] [<outputFile>]
      [<inputFile>]
      [<outputFile>]
  -c, --close=<macroClose>   the macro closing string
  -d, --depth=<depth>        directory traversal depth, default is infinite
      --dry-dry-run          run dry, do not execute Jamal
      --dry-run              run dry, do not write result to output file
  -e, --exclude=<exclude>    file name regex pattern to exclude from the
                               processing
  -f, --file                 convert a single file, specify input and output
  -g, --debug=<debug>        type:port, usually http:8080
  -h, --help                 help
  -i, --include=<include>    file name regex pattern to include into the
                               processing
  -o, --open=<macroOpen>     the macro opening string
  -r, --transform=<transform> [<transform>]
                             transformation from the input file name to the
                               output file name
  -s, --source=<sourceDirectory>
                             source directory to start the processing
  -t, --target=<targetDirectory>
                             target directory to create the output
  -v, --verbose              verbose output
  -x, --regex                interpret transform, include and exclude options
                               as regex

The command line can contain options and parameters. Most of the options have single character version and also multiple character versions. The option values have to be written after the option in case the option is single character and with a = is multiple-character.

The options you can use with the command line version of Jamal are the followings:

  • --dry-dry-run will tell Jamal to perform a dry run without invoking the conversion. Use this opition to test the input and output pattern to see which files will Jamal process and what output files it will create.

  • --dry-run is a dry run, but not so dry as --dry-dry-run. When this option is used the Jamal processing is performed, but the result is not saved into the out. Using this option you can see what files Jamal will process and you can also see if there is any error during the processing.

  • -c or --close=<macroClose> specifies the macro closing string. The default macro closing string is }. When using this option mind that some characters need escape on the command line.

  • -o or --open=<macroOpen> specifies the macro opening string. The default macro opening string is {. When using this option mind that some characters need escape on the command line.

  • -f or --file instructs Jamal not to parse the directory for input files. When this option is used Jamal will process the command line parameter <inputFile> and it will write the output to <outputFile>.

  • -s or --source=<sourceDirectory> specifies the source directory where Jamal will start looking for input files. The file listing is recursive going into subdirectories. The default value is the current directory.

  • -d or --depth=<depth> limits the dept of directory recursion. The default value does not limit the depth.

  • -e or --exclude=<exclude> exclude the files that match the pattern <exclude>. The pattern can be usualy file matching wild-card pattern or regular expression if the option -x, --regex is used. The default value is not to exclude any file.

  • -i or --include=<include> ` include the files that match the pattern `<include>. The pattern can be usualy file matching wild-card pattern or regular expression if the option -x, --regex is used. The default value is *.jam.

  • -r or --transform=<transform> [<transform>] define one or more transformation. When multiple files are processed this transformations are used to calculate the output file name from the input file name. The option must have two values. The first value is the regular expression, the second parameter is the replacement string. These are the parameters that will be used in the Java method inputFileName.replaceAll(a,b) to calculate the output file name. The default value is \.jam$ and an empty string. The default value will cause replaceAll to chop off the .jam extension from the end of the file. That way, for example pom.xml.jam will be converted to pom.xml.

  • -t or --target=<targetDirectory> can specify the target directory where the output will be stored. If input files are under some subdirectories of the <sourceDirectory> then the same directory structure will be created for the output. The default value is the current directory.

  • -x or --regex use regular expression for the <include> and for the <exclude> values. Transform is always interpreted as regular expression.

  • -g or --debug=<debug> start the code in debug mode. To use this option the debugger module implementing the debugger must be on the classpath. This is automatically ensured when Jamal is started using jbang using the jbang jamal@verhas command. The parameter <debug> is the debugger configuration string. To use the web based debugger you can specify http:8080. With that parameter the debugger will start to listen on the port 8080 on the localhost ip. The client code that runs in the browser can also be downloaded from the same server from the http://localhost:8080 address. If you specify a different port, then from that port.

  • -v or --verbose verbose output

  • -h or --help help

  • <inputFile> the input file in case the option -f or --file was used

  • <outputFile> the output file in case the option -f or --file was used

Starting with JBang

JBang (https://www.jbang.dev) is a popular command line tool that eases the startup of Java applications. Jamal can be started using JBang.

This may be the choice for you if you want to use Jamal, but you do not even have Java installed. Installing JBang is extremely simple. When running Jamal using JBang, Jbang will install everything that is needed to execute Jamal is a clean and non-intrusive way.

To start Jamal when you have JBang installed on your machine the command line to start Jamal is

jbang jamal@verhas ... options ...

This command will invoke the command line version automatically caring about all the Jar files. The syntax and meaning of the options are the same as in case of the command line version. This startup also loads all the Jamal extensions, including snippet, scriptbasic, groovy, ruby, plantuml, and debug (1.7.3 and later) and some others. If you want to see the exact list of the modules this startup loads have a look at the starter file.

Note
The possibility to start Jamal using JBang was developed for the version 1.7.3, and it was retrofitted for the version 1.7.2 before the release of 1.7.3.
Note

If you have used Jamal with jbang before then jbang will store its catalog file in the local cache. When you start Jamal using jbang jamal@verhas …​ and you see an old version starting then delete the file

~/.jbang/cache/urls/d917b991facb86b9860fa179df2c804fc2090cc76a83fb15b49f47cc2e885f7c/jbangstarter.java

After that you can start jbang again. It will download the new catalog, always pointing to the latest release.

Debugging Macro Conversions

When something goes wrong, Jamal gives you a detailed error message. The message will include the file name, line number, and character count where the error happened. In other cases, Jamal may think it works fine, but the output is not exactly what you expected. Sorry, in this case, the issue, most probably, is with your expectations.

In cases like that, you can try to debug the execution of the macro engine. There are two possibilities:

  1. use the trace functionality, or

  2. use the debugger.

The trace functionality can create a detailed XML trace of the execution that can later be examined. The trace information is structured with nested structures. XML is a format that can accommodate such nested structures and has very extensive editor support.

The debugging functionality can execute the macro transformation step-by-step providing interactive debugger user interface. The tracing functionality was developed earlier and its importance lessens by the introduction of the debugger.

Tracing

To get a trace file during the execution of Jamal you can

-Djamal.trace=tracefile.xml

on the command line that starts Jamal. It will specify a trace file, in this case, tracefile.xml. If it is more convenient, you can also specify the trace file using the environment variable:

export JAMAL_TRACE=tracefile.xml

The environment variable is taken into account only if the jamal.trace system property is not defined.

The trace file will contain all the macro evaluations' inputs and outputs. Since there can be many Jamal evaluations one after the other, Jamal does not overwrite old trace information. It appends the new trace information. Before starting Jamal, you can manually delete the trace file. Trace files grow large quickly. If you do not want to trace anymore, do not forget to unset the environment variable typing

unset JAMAL_TRACE

to avoid an excessively large trace file growing on your disk.

Debugging

To debug a Jamal macro processing you have to start Jamal in debugging mode. Jamal switches on debugging mode if the system property jamal.debug or the environment variable JAMAL_DEBUG is defined. The value of the property or the variable controls which debugger starts and how.

Currently, there are two debuggers implemented:

  1. web based debugger with UI written in React.js

  2. a TCP/telnet based debugger.

The detailed technical documentation of the server side of the debuggers is described in the document debugger readme. The TCP based debugger is only for experimental purposes or when the web based debugger is not available.

Here we briefly describe the web based debugger. Since the UI can best be described with pictures, especially with moving pictures the documentation is created in screen capture videos.

To start Jamal in debugger mode you can specify

-Djamal.debug=http:8080

on the command line that starts Jamal. It will specify the web based debugger, hence the http and the port, in this case 8080. If it is more convenient, you can also specify the http:8080 debug option file using the environment variable:

export JAMAL_DEBUG=http:8080

The environment variable is taken into account only if the jamal.debug system property is not defined.

If you do not want to debug anymore, do not forget to unset the environment variable typing

unset JAMAL_DEBUG

In debug mode Jamal stops twice for each macro evaluation. Once when it selects the next text without macro from the actual start of the input, or a macro at the start of the input. Second time when the macro was evaluated, and the text is appended to the output.

If you open your browser after you started Jamal in debug mode and try to open the url http://localhost:8080 you will get the debugger UI in your browser. This user interface will let you see the current input, the current output, the defined built-in and user defined macros. You can let the code run, make one step macro evaluation, go into nested macro evaluation, evaluate text interactuvely in the current evaluation envirionment and so on.

2. Simple Example

As a quick sample to have a jump start what Jamal can do:

{@define fruit(color,name,actualSize)=we have an color name of size actualSize}
{fruit/red/apple/20ounce}
{fruit/green/melon/1kg}

will be converted by Jamal to the file

we have an red apple of size 20ounce
we have an green melon of size 1kg

In this sample, the built-in macro define is used to define a so-called user-defined macro fruit. This macro has three arguments named color, name, and actualSize. When the user-defined macro is in use, the actual values replace these arguments.

Note that the macros open with the { character and close with the } character in this example. These are not hardwired in Jamal, and there is not even a suggested default for that. The embedding application has to define the opening string and the closing string. For example, the embedding Java::Geci application uses {% and %} as macro open and macro close strings. It does it because the { and } characters frequently appear in the Java source code. On the other hand, Java code rarely uses the {% or %} format. In this documentation, we use the { and } strings.

However, you have to be aware that this is NOT enforced. It is not even a recommendation or a convention. You can specify the macro opening and closing string as the program parameter, and the Jamal source code can also change it. You can change them using the built-in sep macro (see later) in the Jamal source.

There is one exception where Jamal uses { and } as hardwired strings for macro opening and closing. This exception is implemented in version 1.5.0 and later. When you import a file into your code, and the imported file starts with the characters {@, the import will use { and }. This way, you can easily import files from external sources, like a JAR file or via the web. The package that defines an import file can use the { and } characters. Even if your Jamal file uses different macro opening and closing strings, you do not need to change it to { and } in this particular case. You may have [[ and ]] as opening and closing strings. In this case, you write [[@import res:MyResource.jim]] as an example, and it still will be imported correctly.

The parameters are separated using the first non-space, non-alphanumeric character following the macro’s name in the macro use. Thus, you can write

{fruit/red/apple/20ounce}
{fruit|red|apple|20ounce}
{fruit.red.apple.20ounce}
{fruit :red:apple:20ounce}

the output will be the same for each line:

we have an red apple of size 20ounce
we have an red apple of size 20ounce
we have an red apple of size 20ounce
we have an red apple of size 20ounce
Note
In the last example, we used the : character as the separator. When the name of a macro contains one or more : characters, then the macro is global. Since this character can also be part of the macro’s name, there must be a space before it.

There are also some other rules that make it possible to use a space as separator character. When a macro has exactly one argument then the parsing follows special rules. It is also possible to invoke a macro with more or less number of arguments than are defined using the option lenient. These are advanced topics and are detailed later.

3. Other Macros

define is not the only built-in macro in Jamal. The comprehensive list of built-in macros are

You use the built-in macros with # or @ in front of the macro’s name. These characters signal that the macro is built-in (as opposed to user defined). The typical use is to start a macro with the @ character. In that case, the macro evaluates the rest of the input till the matching closing string.

evaluationorder

If the macro starts with the # character, then the input is first parsed for other macros. These macros are evaluated, and their results replace their occurrences in the code. Only after this, the macro we are looking at is evaluated.

For more about definition scopes and exporting, read the section about export. In that section we discuss the evaluation order of the macros in great detail.

i. comment

since 1.0.0 (core)

comment is used to insert comments to the input. It can also be used to enclose definitions without side effects, but this is not recommended. For that purpose, use the [block](#block) macro.

For more about definition scopes and exporting, read the section about export. In that section we discuss the evaluation order of the macros in great detail.

this is some {@comment this text
will not appear in the output}text

will generate

this is some text

Note that this is important to use the @ character in front of the keyword comment to make it a real comment. If the macro character # is used, like {#comment comment_text} then the comment_text part will be evaluated. If there is some macro in the comment_text that modifies the evaluation state, then the modification will happen. For example, if the comment_text defines some global macro, then the defined macro can be used after the comment block.

It is safe to say always to use {@comment …​}. When the code needs the evaluation, then use the [block](#block) macro.

ii. block

since 1.0.0 (core)

block is technically the same as comment. It is recommended to use the comment macro with the @ starting character. In that case the content of the comment is not interpreted by Jamal. Use the block with # to have the content interpreted. Block should be used to enclose definitions to a scope level. Note that the result of the macro {#block …​ } is an empty string.

For more about definition scopes and exporting, read the section about export. In that section we discuss the evaluation order of the macros in great detail.

iii. begin and end

since 1.0.0 (core)

The macros begin and end start and close a local definition scope. This is similar as using a {#ident …​ } macro to create a new scope for the evaluation of the macros inside it. The text between the {@begin} and {@end} will be evaluated in a new scope. Any user defined macro in this scope is going to be local, unless exported or has a : in the name.

It is recommended to use begin and end when the structure is complex, and it is more readable to use the begin, end macros than a simple block. To ensure that all begin has an end you can name the blocks. You can put an arbitrary string after the macro name begin and if you do then you have to repeat the same string after the macro name end. The spaces from the beginning, and the end of the parameter are trimmed.

{@define Z=1}
{@begin alma}
   {@define Z=2}{Z}
   {@define S=2}{@export S}
{@end alma }{Z}{S}

will result

   2

12

First Z is defined to be the string "1" (without the quotes). Then we start a new scope, named alma. Inside this new scope we redefine the macro Z to be 2. When we use Z writing {Z} then it will output 2 here. We also define S to be 2 and we also export it. Exporting means that the definition will get to the surrounding scope. After that we close the scope named alma. When closing the scope there is an extra space after the name, but it does not matter. Now S is 2, because it was exported and Z is 1, because it was defined to be 1 on this level and was not exported from the nested level.

For more about definition scopes and exporting, read the section about export. In that section we discuss the evaluation order of the macros in great detail.

Scopes are nested, stacked into each other any levels. Scopes are opened by many things, like macro start, or including a file. You can close a scope using the macro end that was opened with a matching begin. You cannot not close a scope using end that was opened by something else. For example, you cannot get into the scope of the including file putting a pair-less end macro into an included file. This will trigger a processing error. It is also an error if a {@begin…​} does not have its {@end…​} pair in the main file or in any included or imported file.

iv. define

  • since 1.0.0 (core)

  • since 1.6.4 default as special macro

  • since 1.7.4 default macro first argument, macro can be defined to evaluate verbatim

  • since 1.7.6 optional and extra ignored arguments

Define basics

define defines a user-defined macro in the current scope.

For more about definition scopes and exporting, read the section about export. In that section we discuss the evaluation order of the macros in great detail.

The syntax is

{@define id(arguments)=body}

or

{#define id(arguments)=body}

The arguments part is optional in case there are no arguments for the macro. In that case the macro syntax is

{@define id=body}

or

{#define id=body}

or

{@define id()=body}

or

{#define id()=body}

Using the () characters after the identifier of the macro is optional, and the result is exactly the same as if it omitted. The two definitions are equivalent.

Note
There is one exception, when you have to use () even for empty parameter list. This is the case, when the id ends with a colon :. In this case the definition {@define id:=…​} would be ambigous, because using := instead of = has a special meaning (see it later).

When the macro is used, the arguments are replaced in the body by the actual parameters supplied at the place of use. The arguments are specified as a comma-separated list. They are usually identifiers.

Note that the arguments do not have any special syntax. The only requirement is that they do not contain a comma ,, a closing parenthesis ) and they do not start or end with …​. That is because the list is comma-separated, because ) terminates the list of the arguments, and a …​ prefix or postfix denotes optional arguments. It is recommended, though, to use normal identifiers and no spaces in the argument names. This is only a recommendation and is not enforced by Jamal. You may need to process some special text. You may need some specially named arguments. In the examples, you usually see that the arguments start with a $ character.

Somebody may follow other conventions, like starting every argument with the * or enclosing the argument names between | or / or some other characters. These practices can be absolutely okay so long as long they support the readability of the macro body and the use of the macro. Applying such practices may help to visually separate the macro arguments from the textual content of the macro body.

From practice, we see that in case of longer macros using simple, argument names with one or only a few letters may lead to some error. For example the macro:

{@define fox(x)=The brown fox jumps over the high x}{fox fence}

will result

The brown fofence jumps over the high fence

This is probably not the result that the macro creator wanted. They probably missed the point that the word fox also contains an x.

To ensure that the argument replacing is consistent, the argument names cannot contain each other as a substring. Assume that there is an argument a with an actual parameter value oneA. There is another argument named aa with an actual value twoAs. In this case the occurrences of aa in the body could be replaced to twoAs or oneAoneA.

Although Jamal could define some rule, like left-to-right, or right-to-left, or longer-first evaluation these could still lead to hard to read situations. Jamal suffers from hard to read situations already without this extra headache. To avoid that Jamal does not allow you a and aa as argument names to a macro definition the same time.

During the replacement a parameter may be a string that contains the name of one or more argument names. This is absolutely legit. These will NOT be replaced with the parameter value(s) that were provided for the other argument(s) that are inside the value of the parameter. For example:

{@define z(*a,*b,*c,*d)=When a *a can *b then *c can *d}
{z /leopard and a *c/run/fish/fly}

will result

When a leopard and a *c can run then fish can fly

even though *c is a fish, but the characters *c in the output come from the value of a parameter and therefore it is not replaced.

Special User Defined Macros

In Jamal user defined macros are defined using the define macro. Internally, a user defined macro can be anything that implements a specific Java interface. Jamal only requires that it can be evaluated and that it handles the string arguments passed to it. Some built-in macros implemented in external packages, like the Yaml package have their implementation. It means that they create user defined macros that you can pass parameters, and their evaluation results some output, but they are not "classical" user defined macros. They just behave like the macros that are defined using define. Foe example the macro `counter:define`from the Snippet package creates a "user defined" macro that results a number when used, but it also changes the value at every invocation.

Macro redefine

Macros can be redefined at any point. For example

{@define a=1}{@define a=2}{a}

will result

2

It is possible to use a question mark ? after the macro keyword define. In that case the macro is only defined if is NOT yet defined in the current scope or any other outer scope.

{@define a=1}{@define ? a=2}{a}

will result

1

It is also possible to use a ! instead of the ?. In this case the macro define will report an error if the macro is already defined.

{@define a=1}{@try! {@define! a=2}}

will result

The macro 'a' was already defined.
Note
The macro try! will catch the error and send the error message to the output. This is mainly used for debugging and in this case for documentation purposes.

When a user defined macro is evaluated, the result of the macro is evaluated again resolving all the macros that happen to be in the result. This can be prevented using the verbatim macro. You can also read more details on the macro evaluation order in the chapter verbatim.

If you use the ~ (tilde) character after the keyword define then the macro will be evaluated "verbatim" by default. It means that the value of the user defined macro will not be evaluated like if it was used with the macro verbatim. For example:

{@define x=1966}
{@define a={x}}
{a} evaluates first to the macro `x` and then that evaluates to 1966
{@verbatim a} stops before the evaluation of the result of the macro and this way it is the same as
{@define ~ a={x}}{a}

will result

1966 evaluates first to the macro `x` and then that evaluates to 1966
{x} stops before the evaluation of the result of the macro and this way it is the same as
{x}

If, for any reason, you need to evaluate the result of such a macro you can use eval or ! when using th e macro.

{@define x=1966}
{@define a={x}}{a} is the same as
{@define ~ a={x}}{!a}

will result

1966 is the same as
1966
Note

You cannot use ! together wit the macro verbatim. This is because the format {!@verbatim …​} is the same as simply {…​} without the ! and the @verbatim. On the other hand when the macro would almost always be used together with verbatim then it makes sense to define the macro to be a verbatim user defined macro. In the few cases when it would be used without verbatim you can use !.

The macros in the module Yaml define the object structures read from the Yaml format as verbatim user defined macros. When you use such a macro, like {yaml} the Yaml formatted string representation of the data is the result of the macro. Yaml itself may use the JSON compatible {A:1, B:2, …​, X:88} format, which Jamal may mistakenly try to interpret as a macro. To prevent this these Yaml data containing user defined macros are verbatim by default.

Global Macros

When the name of the macro contains at least one colon character : then the macro will be defined in the global scope. Global scope is the top-level scope, and it means that a macro like that can be used everywhere in the text after it was defined.

For example modifying a bit our example from the "begin and end" section

{@define A:Z=1}
{@begin alma}
{@define A:Z=2}{A:Z}
{@end alma }{A:Z}

will result

2
2

In this case the macro A:Z is a global macro because it has a : in the name.

It is also possible to define a user-defined macro to be global without : in the name. If the very first character of the name of the macro is : then this character is removed, but the macro is defined in the global scope. Further modifying the example we get:

{@define :Z=1}
{@begin alma}
{@define :Z=2}{Z}
{@end alma }{Z}

which will result:

2
2

Note that you cannot use {:Z} when using the global macro. The : character in this case is not part of the name. Also note that you can define a local macro even if there is a global macro of the same name. For example

{@define :Z=1}
{@begin alma}
{@define Z=2}{Z}
{@end alma }{Z}

which will result:

2
1

The define inside the begin, and end delimited scope does not redefine the global scoped Z. It defines a scope local macro, which gets out of scope with the macro end.

When a user-defined macro is used, the parameters are defined after the name of the macro. In the case of user-defined macros, there is no @ or # in front of the name of the macro. Optionally there may be a ? character. In that case, the result of an undefined user macro will be the empty string. In most other cases using an undefined user macro results an error.

{@try! {undefinedMacro}}
this is empty string >>{?undefinedMacro}<<

which will result:

User defined macro '{undefinedMacro ...' is not defined.
this is empty string >><<
Note
The try built-in macro is mainly for debugging purposes and returns the error message itself.

If you automatically want to interpret all user defined macro reference if there was a ? in front of them then you can use the option emptyUndef. With this option there is no need for the ? in front of the macro name, every undefined macro will evaluate to empty string.

{@options emptyUndef}>{?notDefined}<>{notDefined}<

results

><><
Default macro

In addition to having a ? character or using the try macro, there is another possibility to avoid the error in case of an undefined macro. If the macro default is defined then it will be used instead of any undefined macro, even when the ? character is used in front of the macro name.

Example:

>>{?hoppala}<<
{@define default=wupppss}{hoppala}
>>{?hoppala}<<

Result:

>><<
wupppss
>>wupppss<<
Note

During the design there were two possibilities. One, to let the {?…​} macro use perform the same way as if there was no defined default macro. The other, to let the {?…​} use the default macro. We selected the second option because in that case there is a fallback. You can simply write {#ident {@undefine default}{?…​}}. If we selected the first option, then the {?…​} macro would not have and alternative way to use the default macro.

{@define default=wupppss}\
{#ident {@undefine default}>>{?hoppala}<<}>>{?hoppala}<<

results

>><<>>wupppss<<

Because it is cumbersome to write {#ident {@undefine default}{?…​}} every time the option :noUndefault can also be used.

{@define default=wupppss}\
{@options :noUndefault}>>{?hoppala}<<{@options ~:noUndefault}>>{?hoppala}<<

results

>><<>>wupppss<<

The macro default can have arguments, and they will be handled as they should be.

{@define default($x)=wupppss $x}{hoppala zumzum}
>>{?hoppala zumzum}<<

Result:

wupppss zumzum
>>wupppss zumzum<<

Note that there can be many undefined macros, and the different macros may expect different number of parameters. If the number of the actual parameters is not the same as what the defined default expects Jamal will stop with error. Consider the use {@options :lenient} along with the definition of the default macro, or a default macro with optional arguments.

Starting with the version 1.7.4 the macro default can have a special first parameter. If the first argument of the macro is either $macro or $_ then this parameter will hold the name of the macro, which was not found. That way the default macro can use the name of the macro in its evaluation.

Example:

{@options :lenient}
{@define default($_,$x)={@if |$x|<$_>$x</$_>|<$_/>}}{hoppala}
{bikkala zz}

Result:

<hoppala/>
<bikkala>zz</bikkala>

Starting with the version 1.7.6 Jamal introduced optional arguments to user defined macros. (Details are a bit later.) You can use optional arguments when you define a default macro. For example:

{@define default(...)=DEFAULT}{huppala}{bumbala}{wopsydosy}

will result

DEFAULTDEFAULTDEFAULT

The parameters stand after the name of the macro separated by a separator character. The first non-whitespace and non-alphanumeric character after the name of the macro is the separator character. It can be / as in the examples below, but it can also be any non-alphanumeric character. The number of parameters should be exactly the same as the number of argument unless the {@options :lenient} was specified, or the …​ was used to denote optional arguments. In the case of optional arguments, the missing arguments will be zero-length strings. If there are extra parameters, they will be ignored.

The separator character cannot be an alphanumeric character (letter or digit, Unicode categories Lu, Ll, Lt, Lm, Lo, and Nd). Any other Unicode character can be used as a parameter separator character.

If the user-defined macro has exactly one argument then there is no need to use a separator character. The sole parameter of the macro can start after the name of the macro at the first non-whitespace, alphanumeric character. For example,

{@define enclose(a)=<!!a!!>}
{enclose this text}

will result

<!!this text!!>

The parameter, in this case should start with an alphanumeric character or with a macro start string. If it starts with something else then that character will be the separator character that separates the parameters. In this case, because there is only one parameter it will separate the macro name from the parameter. For example,

{@define enclose(a)=<!!a!!>}
{enclose /-}

will result

<!!-!!>

Writing

{enclose -}

will result

<!!!!>

because - is not alphanumeric and therefore it is treated as a separator character separating a single empty string. On the other hand

{@define enclose(a)=<!!a!!>}
{@define dash=-}
{enclose {dash}}

will work, and the result will be

<!!-!!>

This is because the { in this case is the macro start string. in that case, the first character of it is not considered to be as a separator character even though it is not alphanumeric.

There are cases when it is necessary to use a separator character. In some cases the parameter starts with a significant space. In other cases it starts with a character that is not alphanumeric. In that case the above macro should be used like the following three examples:

{enclose |+this text}
{enclose ||this text}
{enclose | this text}

These uses of the above macro will result

<!!+this text!!>
<!!|this text!!>
<!! this text!!>

In the second line in the examples, the separator character is used in the parameter. Because the macro needs only one argument all the rest of the parameter until the macro closing string is used as the single parameter. It is not split up further along the later occurrences of the separator character. Just use any non-alphanumeric character in front of the parameter that looks good. You need not worry that the character itself presents in the content.

{@options ~lenient}
{@define x(a,b)= |a b|}
{@try!{x/s/h/t}}

will result an error, because there are too many arguments:

Macro 'x' needs 2 arguments and got 3
>>>s
>>>h
>>>t
Note
In the example above we switched off the lenient mode, because it was already switched on for some previous samples.

The rule that the separator character is not considered as another separator in the rest of the argument is valid only when there is only one argument. In case of multiple arguments this could easily lead to unreadable macro use. The above example modified to be lenient demonstrates this:

{@define x(a,b)= |a b|}{@options :lenient}
{x/s/h/t}

will result

|s h|

The provided third value, t is ignored.

There are situations where the use of a separator character is not a must, but the use of it helps the readability. Consider, for example {enclose/a/b/v}. We know from earlier that enclose has only one argument, however the use of it looks like it has three. The one argument it has is a/b/v.

Omitting the separator character, / in this case, does not help the readability or only a bit. The use {enclose a/b/c} still looks like a macro with three parameters. In situations like that the most readable solution is to use an explicit separator character that looks good. For example {enclose |a/b/c} makes it evident and readable that there is only one parameter: a/b/c.

In the following sample code, you can see some complex examples that demonstrate these cases:

{@define parameterless=this is a simple macro} macro defined
{parameterless}
{@define withparams(a,b,%66h)=this is a b %66h} macro defined
{withparams/A/more complex/macro}
{withparams/%66h/%66h/zazu} <- %66h is not replaced to zazu in the parameters
{@define? withparams(a,b,c)=abc}here 'withparams' is not redefined
{withparams|a|b|c}
{#comment {@define x=local}{@define :x=global} {#define :y=here we are {x}}}
{y}
here we are {x}

will generate

 macro defined
this is a simple macro
 macro defined
this is A more complex macro
this is %66h %66h zazu <- %66h is not replaced to zazu in the parameters
here 'withparams' is not redefined
this is a b c

here we are local
here we are global

This is a fairly complex example. To ease the understanding note the followings:

  1. %66h is an absolutely valid macro parameter name. Anything can be a parameter name that does not contain a comma, a closing parentheses, does not start or end with …​ and is not a substring of any other parameter.

  2. When a macro parameter is replaced in the body of the macro the processing of that string is finished and is not processed further replacing macro parameters. Macro parameters are only replaced with the actual values in the macro body and not in the parameter actual values. That is why parameters a and b are replaced with the actual string ' %66h' but then this is not replaced with the actual value of the parameter %66h.

  3. When we define the macros x and y inside the comment macro it happens in a local scope of the comment macro. It means that the definition of x has no effect outside the macro comment. Using the name :x defines the macro x in the global scope, that is above the current scope. When we defined the macro y it also starts with : and so it gets into the global scope. However, during the definition, it is in the local scope of the comment macro where the local definition of x overrides the global definition of x even though the global definition happened later. Therefore, y will be here we are local. That is also because y is defined using the # character before the built-in macro keyword define and thus the content of the definition is evaluated before defining the global y.

Pure Macros

It may happen that the macro opening and closing strings are different when the macro is defined and when used. In a situation like that the macro evaluation replaces the macro opening and closing strings in the macro definition to the actual macro opening and closing strings. It can be prevented using := instead of a = between the name, parameter list and the body of the macro.

{@sep [ ]}[@define a=[z]{z}][@sep]{@define z=3}{a}
{@sep [ ]}[@define a():=[z]{z}][@sep]{@define z=3}{a}

results

3{z}
[z]3

When a is evaluated the result is [z]{z} on both lines. In the next step this result is evaluated, because the macro is not a verbatim one. In the first case the macro a normal one and the evaluation knows that the macro opening and closing strings were [ and ]. In this case the evaluation also knows that the characters { and } are just ordinary characters.

In the second case, however, the macro is a "pure" macro and is evaluated as it using the current macro opening and closing strings.

Note that when there are no parameters, and the macro definition does not use the optional () after the name of the macro the := would be ambiguous. To avoid this ambiguity you have to use () after the name of the macro if the name of the macro finishes with a : character.

Optional Arguments

Setting the option lenient is a very aggressive way to make all macros inside the current scope evaluated in the lenient way. There are more subtle methods to specify that some macro may work with less or more actual parameter values than their concrete arguments. Macros can define a minimum, and a maximum number of parameters they need when they are called. When an argument in the define macro starts with …​ characters it means that the next argument, and the arguments afterwards are optional. When using the macro these arguments will be empty string when not provided. For example,

{@options ~lenient}{@comment just to be sure}
{@define a(a,b,...c,d,e)=>a< .b. /c/ |d| (e)}
{a :1:2:3}

will result

>1< .2. /3/ || ()

You can also say that all the parameters are optional in case the …​ is in front of the first argument:

{@define a(...a,b,c,d,e)=>a< .b. /c/ |d| (e)}
{a :1:2:3:4:5}
{a :1:2:3:4}
{a :1:2:3}
{a :1:2}
{a :1}
{a}

will result

>1< .2. /3/ |4| (5)
>1< .2. /3/ |4| ()
>1< .2. /3/ || ()
>1< .2. // || ()
>1< .. // || ()
>< .. // || ()

Optional parameters are different from leinent mode, that they do not allow extra parameters. For example the

{@try! {a :1:2:3:4:5:6}}

will result the error

Macro 'a' needs (0 ... 5) arguments and got 6
>>>1
>>>2
>>>3
>>>4
>>>5
>>>6

If you want to allow extra parameters then you can append …​ after the last argument:

{@define a(...a,b,c,d,e...)=>a< .b. /c/ |d| (e)}
{a :1:2:3:4:5:6}

resulting in

>1< .2. /3/ |4| (5)

Appending …​ after some other argument, which is not the last one or using …​ prefix on more than one argument is an error. It is also an error to add …​ postfix after the first argument when the macro has one argument. One argument macros are treated in a special way and all the text following the macro will be treated as a single argument, thus it is meaningless to use …​ after a single argument.

v. undefine

  • since 1.6.6

undefine can be used to undefine a macro. Undefining a macro works the same way as definition: in scope. When you undefine a macro it will be undefined only for the current scope and later for any lower newly opened scope. Undefining a macro does not affects the definition of the macro in any higher level.

You can undefine a macro on the global level the same way as you can define a macro on the global level.

Simple undefine on one single scope
{@define fruit=apple}{fruit}{@undefine fruit} |{?fruit}|

Here we define the macro fruit to be apple, and we use it once. Following it, we undefine the macro. When we use it next time it is undefined. The use of it is not an error because we use the ? in front of the macro name, but the result is the empty.

Finally, it will generate the following output.

apple ||
Undefine an inherited macro in a local scope
{@define fruit=apple}{fruit} {#ident {@undefine fruit} |{?fruit}|}  |{?fruit}|

In this example we define the macro apple on the top level scope, but we undefine it one level deeper. The macro is undefined only in the local scope, where it was undefined but on higher levels it is still defined.

Finally, it will generate the following output.

apple ||  |apple|
Being undefined can be exported
{@define fruit=apple} {fruit}\
{#ident {@undefine fruit} |{?fruit}| {@export fruit}}\
|{?fruit}|

In this example we undefine the macro fruit in the local scope, but then we export it from this scope. Being explicitly undefined can be exported the same way as the macro, which is defined. Because the "undefinedness" is exported the macro fruit becomes undefined in the enclosing scope.

Finally, it will generate the following output.

apple|| ||
Undefine, export and redefine
{@define fruit=apple}\
global scope: {fruit}
 {@begin scope_1}\
   scope_1: {fruit}
   {@begin scope_2}\
     scope_2: {fruit}
     {@undefine fruit}{@export fruit}\
     scope_2: {?fruit}
     {@define fruit=pear}\
     scope_2: {fruit}
   {@end scope_2}\
   scope_1: {?fruit}
 {@end scope_1}\
global scope: {fruit}

In this example we define the macro fruit on the top level. After that we open two new scopes nested. We undefine the macro in the most inner scope, and we export this undefinedness to the middle scope. After that, we define the macro again in the most inner scope.

At this moment we have three "definition" of the macro fruit. In the outer scope it is defined to be apple. In the middle scope it is undefined. In the most inner scope it is defined to be pear.

Finally, it will generate the following output.

global scope: apple
    scope_1: apple
        scope_2: apple
          scope_2:
          scope_2: pear
      scope_1:
 global scope: apple
Note

For the technically savvy, the following may help get a more comfortable grab of how the macro undefined works.

Jamal stores user-defined macros in maps. The key in the map is the id of the macro. The value in the map is a Java object that represents the user-defined macro. The maps are organized in a stack. The stack has one element for each scope. When a new scope opens in the Jamal source, the stack grows. When a scope is closed, the stack shrinks. Searching for a macro starts in the map stored at the top of the stack (opened latest). If the macro is not in the map, then the search goes deeper. The search finally finds the macro in one of the maps or runs out of stack levels.

The maps are very general in the sense that they can store any Java object that implements Identified. User-defined macros implement this interface along with UserDefinedMacro. A macro is undefined when there is no object assigned to the name in any of the maps. However, it is also undefined when the search finds an object in the stacked map structure that does not implement UserDefinedMacro. The macro undefine inserts an object into the structure that is exactly like that. Export is possible because the macro export does not care about the implemented interfaces. It merely removes the object from the map and inserts it in the map on the next stack level.

vi. eval

since 1.0.0 (core)

eval interprets the content of the macro. The text written after the macro keyword eval is evaluated as a script. The scripting language can be defined following a / character. If there is no script type defined (or jamal is defined) then the content will be evaluated as normal Jamal macro text. Otherwise, the script engine named is used.

There are three ways to use the macro in one of the following formats:

eval macro text
eval/scripttype script
eval* text

If eval is followed by / character then the next identifier is the type of the script. White space characters before, and after the /, as well as after the script type name are ignored. You can use any scripting language that

  • implements the Java scripting API and

  • the interpreter is available on the classpath when Jamal is executed.

If the script type is jamal then it is the same as if there was no script type specified. You may need the explicit specification of jamal when the content of the macro to be evaluated starts with the / character.

If character following the keyword eval is * then the scripting type is jamal and the evaluation is repeated until all macros are resolved. The macro assumes that all the macros are resolved when the evaluation of the text does not change any more. This may lead to infinite loop, therefore there is a built-in limit. eval* evaluates the macro input at most 100 times. This limit can be changed with the option evaluateLoopLimit. This name can also be used as a user defined macro to set this option globally, for example:

{@define `evaluateLoopLimit`=60}

This option has two aliases limit, and max. The aliases can be used interchanged between [ and ] characters following the \* character, for example:

{@eval* [limit=60] evaluate this max 60 times}

The following two examples show how eval can be used to evaluate simple arithmetic expressions using the Java built-in JShell interpreter. Note that in the second example the macro eval is preceded with the character # therefore the body of the macro is parsed for other macros before eval itself is invoked. That way {a} and {b} are replaced with their defined values and eval itself sees 1+2.

{@eval/JShell 1+3}
{@define a=1}{@define b=2}\
{#eval/JShell {a}+{b}}

will result

4
3
Note
Versions prior 1.5.0 used the Nashorn JavaScript interpreter as the default interpreter.

Starting with version 1.5.0 Jamal introduces the ! modification character. When this character is used in front of a macro, then the result of the macro will be evaluated like it was surrounded with {#eval …​ }. This can be used in the case of user-defined macros as well as in the case of built-in macros. Note, that in the case of user-defined macros the result of the macro will be evaluated by default. Using the ! in front of a user-defined macro will repeat the evaluation. You can use more than one ! characters in front of a macro. The macro result will be evaluated so many times as many ! characters there are. In case of a user-defined macro the "so many times" should be interpreted as one, by default plus N times.

For example:

{@define a=this is it}
{@define b={`a}}
{@define c={`b}}
{@define userDefined={`c}}
{userDefined}
{!userDefined}
{!!userDefined}
{!!!userDefined}

and the output is

{c}
{b}
{a}
this is it

In this example the macro userDefined is {`c}. User defined macros values are evaluated after the evaluation of the macro itself, therefore when we use {userDefined} we get {c}. The back-tick character before the macro after the { is identical to the use of an ident macro: {@ident {c}}.

When there is a single ! in front of it, then the repeated evaluation results {b}, and so on. To get the final result, in this case we need three ! characters, meaning four post evaluation.

You can use this character together with the back-tick macro modifying character. They do not eliminate each other, because the back-tick prevents pre-evaluation and ! provides extra post evaluation. When using ! to evaluate the result of a macro you cannot specify any scripting language. The evaluation will be Jamal macros evaluation.

vii. defer

since 1.10.0

The macro defer evaluates its input only when the processing is finished. It can be used to execute some macros at the end of the execution, which have side effect, or to modify the final output using some macros.

At the place of the the macro the value of the macro is an empty string. The result of the evaluation, since it happens after the whole input was already processed and we have a final output, is also ignored. There is, however, a possibility for the content of the macro to read the final result and also to modify it.

When the input of the macro is executed the global macro $input contains the output of the processing. The naming may be strange at first, but consider that this string is the input for the deferred macro evaluation.

input output

If this evaluation defines the global macro $output the value of the macro will be used instead of the original output.

The name of the input and output macros can be changed using options. The options

  • $input with the aliases input, and inputName can specify the name of the input macro.

  • $output with the aliases output, and outputName can specify the name of the output macro.

As usually the option name can be defined as a macro, like {@define $input=$INPUT}, the aliases can only be used as macro options, like

{@defer [output=OUTPUT] ... }
Note

You probably want to use the macro defer with the @ character in front of the macro name. If you use # then the content is evaluated before the macro defer is executed. In this case the macro sees the evaluated input and will defer the evaluation of that to the end of the execution.

In the followings we will give some examples.

This example is the simplest. It defers an empty string.

{@defer}
Original result.

When the empty string is evaluated nothing happens, the original output is retained:

Original result.

The second example is a bit more complex:

{@define doplikate(a)=aa}\
{@defer
  {#define $output={doplikate/{$input}}}
}\
Is this doplikated?

This example defines a user defined macro that duplicates the input. In the deferred evaluation the macro $output is defined and it will be the same as the $input repeated.

Is this doplikated?Is this doplikated?

The next example demonstrates that

  • the name of the input and output macros can be redefined, and

  • multiple defer macros are executed in the order as they were evaluated in the input during the Jamal processing.

{#block
  {@define $output=OUT}\
  {@define $input=IN}\
  {@defer
    {#define OUT=|{IN}|}\
  }\
}\
{@defer {#define $output=*{$input}*}}\
Framed

Note that the definition of the macros $input and $output are local to the block and therefore they have no impact on the second defer. Since the {#define OUT=|{IN}|} is defind before {#define $output={$input}} the text Framed is enclosed first between | characters and only the result is enclosed between \*.

The output is:

*|Framed|*

The next example is almost the same as the previous. It uses macro options to set the input and output names for the first defer macro:

{@defer [input=IN output=OUT]
  {#define OUT=|{IN}|}\
}\
{@defer {#define $output=*{$input}*}}\
Framed

In this case there is no need for the block macro, since option setting is always local to the macro where it is set. The result is the same as in the previous case:

*|Framed|*

The next example shows that you do not need to use the input at all to set the output.

{@defer
  {#define $output=}{@comment just nothing}
}\
Is this ignored?

In this case the output is an empty string

The following sample shows that the macros used in the text of defer have to be defined only when it gets executed. In the example the macro doplikate is not defined when used in defer only at the end of the file.

{@defer
  {#define $output={doplikate/{$input}}}
}\
Annoying?{@define ~ doplikate(a)=aa}\

The output is:

Annoying?Annoying?

The following example is a bit more complex. In this case the code uses the escape* macro.

{@escape*````}\
{@defer
    {#define $output={doplikate/{$input}}}{@comment DEBUG}
}\
{@escape* ``{mememe}``}Mememe?{@define ~ doplikate(a)=aa}\

In this case there are two deferred operations. The first one is the unescaping of escape*. This is executed first, because the use of the first escape* macro precedes the macro defer. When this unescaping is finished the result of the processing will be {mememe}Mememe?. It contains a string that can be interpreted as a macro. For this reason the macro doplikate is defined as a "verbatim" macro. This is signalled by the ~ character after the define keyword. Verbatim user defined macros are not post evaluated. When doplikate is invoked in the defer then {mememe}Mememe? will be converted to {mememe}Mememe?{mememe}Mememe?. This result also will not be evaluated again.

However, when we set the macro $output in the line {#define $output={doplikate/{$input}}} why {mememe}Mememe? is not evaluated. The reason is that the user defined macro $input holding the final result of the Jamal processing is also a verbatim macro.

The output is:

{mememe}Mememe?{mememe}Mememe?

Although $input is verbatim, $output does not need to be. This macro is used temporarily by the deferred action to change the output of Jamal processing. The following example shows that the value of $output is not available as input for defer. The macro $output can only be set by the input of defer and $output is undefined when the evaluation starts:

{@defer {#define $output=aaa{?$output}}}\
{#define $output=this will not survive}
Annoying?

This example tries to use the value of the macro $output in the deferred code. The deferred code can rely on the macros defined during the Jamal processing. Note, however that only the top level macros are available as all other macros are out of scope and only those, which were defined at the end of the Jama processing.

The macro $output, however, is used in a special way. Because it serves to pass a modified output from the deferred code it is undefined before the deferred code start. The result of this evaluation is:

aaa

The macro $output gets undefined before the evaluation of each deferred code. If we extend the previous example and define the output in one deferred code and try to use that in the next one it will still be undefined.

{@defer {#define $output=this will not survive{?$output}}}\
{@defer {#define $output=aaa{?$output}}}\
{#define $output=this also will not survive}
Annoying?

The output is still:

aaa

The last example shows that other macros survive and can be used in subsequent deferred actions. If the macro doplikate is defined in a deferred action then the subsequent deferred actions can use the macro:

{@defer {#define $output=|{$input}|}}\
{@defer {@define ~ doplikate(a)=a/a}}\
{@defer {#define $output={doplikate {$input}}}}\
wuff

And the output is:

|wuff|/|wuff|

viii. env

since 1.3.0

env returns the value of an environment variable. The macro can also be used to test that an environment variable exists or not. If the argument to the macro is the name of an environment variable then the result will be the value of the variable. If the variable is not defined then the macro will result empty string.

{@env JAVA_HOME}

is

/Library/Java/JavaVirtualMachines/jdk-14.jdk/Contents/Home

on the machine where the original README.adoc.jam file was converted to ASCIIDOC.

If there is a ? after the name of the variable name then the macro will result either the true or false. This can be used to test that an environment variable exists or not. Testing the value of the environment variable in an {@if …​ } macro may be misleading when the value is literal false or an empty string.

Starting with Jamal 1.9.0 it is possible to use ! after the name of the variable. In this case the macro will throw exception when the environment variable is not defined.

ix. import

since 1.0.0 (core)

import opens a file and reads the content of the file and interprets it as Jamal macro file. Anything defined in that file will be imported into the scope of the current file. If the macro opening and closing strings are redefined using the sep macro it will change for the file that imported the other file. Any user-defined macros defined in the top-level scope of the file will be available in the importing file.

Note that the top-level scope of the file may not be the same as the global scope. If the importing happens

  • from an included file, or

  • from inside a block of from inside a macro, or

  • in scope that was started with a begin macro

then the "top-level-scope of the file" is the one, that contains the import macro. If anything is defined into the global scope in the imported file then those macros will eventually be in the global scope and available to anyone later.

The output that the processing of the imported file generates is discarded.

The syntax of the command is

{@import file_name}

The name of the file can be absolute, or it can be relative to the file that imports the other file. Any file name starting with the letters res: are considered to be resource files in Java. This makes it possible to load macros that are provided with JAR libraries and are on the classpath. Any file name starting with the letters https: are downloaded from the net.

The option [top] can be used along with the import. In this case a relative file name is relative to the main file that imports the other files. It is not possible to to step one or a few levels up in the import hierarchy. The only two possibilities are to import as file relative to the current one or the top level one.

Note, however, that using the option [top] does not change the scope of the imports. The definitions will be exported to the importing scope. This option only changes the base directory for the file name calculation.

Use import to import user-defined macro definitions.

Because the textual output from the evaluation of the file is discarded feel free to use text in the file to be imported as documentation. There is no need to enclose such a text into a {@comment …​} macro.

Starting with version 1.5.0 the import macro looks into the file before evaluating it. If the very first two characters in the file are {@ then it evaluates the content using { as macro opening string and } as macro closing string. This way you can freely import resource files provided in JAR file or through the net even if you use different macro opening and closing strings. Such import files cannot redefine the macro opening and closing string unless file importing also uses { and }.

x. include

  • since 1.0.0 (core)

  • since 1.7.3 verbatim include

include reads a file similarly to import, but it starts a new scope for the processing of the included file, and it also results the content of the file included into the main file.

Use include to get the content of a file into the main output.

The file included can define user-defined macros. These macros are available only inside the included file unless they are exported. The included file may redefine the macro opening and closing string, but this works only in the included file only. The file that includes the other file is not affected by the redefinition of the macro opening and closing string.

The macro itself is replaced by the output generated by the processing of the included file.

The syntax of the command is

{@include file_name}

or (starting with 1.7.3)

{@include [verbatim] file_name}

If the option verbatim is specified then the file is inserted into the output as it is without processing. This option can also be used in the form

{@include [includeVerbatim] file_name}

This version of this option name can also used as an option set using {@options includeVerbatim}. In that case all includes will be executed without processing the read file where and when the option includeVerbatim is true.

Note that the macro include is NOT inner scope dependent. It means that {#include {@options includeVerbatim} …​} will not work. The options set inside the include macro have no effect when the include macro is executed.

The option set outside, like {@options includeVerbatim}{#include …​} will work. However it will change the behaviour of all include macros executing later, while the option is in effect.

The name of the file can be absolute, or it can be relative to the file that includes the other file. Any file name starting with the letters res: are considered to be resource files in Java. This makes it possible to load macros that are provided with JAR libraries and are on the classpath. Any file name starting with the letters https: are downloaded from the net.

The option [top] can be used along with the include. In this case a relative file name is relative to the main file that includes the other files. It is not possible to to step one or a few levels up in the include hierarchy. The only two possibilities are to include as file relative to the current one or the top level one.

The number of includes are limited to 100 in depth. A file can include another, which can again include another and so on, but only to the maximum depth of 100. This depth limit is set because an included file can be included many times. It is possible to implement recursion including of files. If the recursion does not end the include macros would drive the macro resolution into an infinite loop. This limit prevents this to happen.

The limit can be modified setting the environment variable JAMAL_INCLUDE_DEPTH.

xi. use

since 1.0.0 since 1.7.4 can define an alias for an already loaded macro

use declares a Java class as a built-in macro or defines an alias name for an already loaded built-in macro.

How macros are loaded

Built-in macros are classes that implement the javax0.jamal.api.Macro interface. When they are registered as services, they are automatically loaded when any application embedding Jamal creates a new processor. In other words, the classes that implement some macros are automatically discovered if

  • they are in the module-info module descriptor provides directive and/or

  • the fully qualified name of the class is listed in the JAR file in the META-INF/services/javax0.jamal.api.Macro file.

Some libraries contain javax0.jamal.api.Macro implementations that are not loaded by the service loader. These classes are not advertised in the module-info file or in the META-INF directory. To use these classes as built-in macros the macro use has to be invoked.

Defining the use of a Java Class as a Macro

The use of the use macro (sic) is the following:

{@use global javax0.jamal.scriptbasic.Basic as scriptbasic}

In this example, the class javax0.jamal.scriptbasic.Basic implements a macro. The class has to be on the classpath, and it has to implement the interface javax0.jamal.api.Macro. It will be defined and available as a globally available built-in macro under the alias scriptbasic.

The keyword global can be missing:

{@use javax0.jamal.scriptbasic.Basic as scriptbasic}

In this case, the macro will only be available in the current scope and will not be available as soon as the current scope is closed. Note that built-in macros cannot be exported. They can be declared either local for the current scope or global.

Usually, the alias part (the as scriptbasic in the example above) can also be omitted:

{@use javax0.jamal.scriptbasic.Basic}

In such a case the macro will be registered with the name that the macro provides by itself as an identifier. The interface Macro defines a method String getId() that should return the identifier of the macro. The interface also provides a default implementation that returns the lower-case version of the class name (w/o the packages). If there is no defined alias following the as keyword then the one returned by the macro implementation will be used.

It is recommended to use the alias in the Jamal source file. That way there is no ambiguity when reading the code what the name of the built-in macro is.

Defining the use of a Java Class as a Macro

The syntax of the command is the same to define an alias for an already loaded macro. If there is no . dot character in the "klass name", then the command will know that it cannot be a class name. In that case it will look for an already loaded built-in macro with the given name and it will register it again with the new alias. Following this both names can refer to the same macro.

The alias will refer to the built-in macro, which is the closest reachable in the current scope. If the evaluation leaves the current scope, and the global keyword was not used then the alias will also go out of the scope. It is independent of the macro itself. The macro may be reachable via the original name.

The alias will refer to the built-in macro, which is the closest reachable in the current scope even if the global keyword is used. In this case the alias will be global, and the macro will be reachable via the alias even if the original name was not registered global and goes out of scope.

xii. script

since 1.0.0 (core)

The macro script defines a user-defined macro that is interpreted as a script. The syntax of the command is

{@script/scripttype id(parameters)=body}

If script is followed by / character then the next identifier is the type of the script. If this is missing the default, JShell is assumed. You can use any scripting language that implements the Java scripting API and the interpreter is available on the classpath.

The parameters are handled differently from the parameters of the user-defined macros defined using the define built-in macro. In that case, the parameter strings are replaced by the actual value strings during evaluation. In this case, the parameters are used as global variable names. Using these names, the actual values are injected into the context of the script before evaluation.

This also implies that you do not have the total freedom of parameter names. For define we can use any string as a parameter id so long as long it contains no , and no ). In this case, you should care about the syntax of the scripting language used. The parameter names have to be valid identifiers in the scripting language as they are used as such.

The value injection converts the actual value of the parameter to script values. Because the parameters are injected into global variables Jamal performs some conversions. Without this, all the scripts that use some integer or floating-point calculation were supposed to convert them first from the string.

Therefore, Jamal tries to convert the actual parameters.

  • First it tries treating it as an integer. If it succeeds then the global variable having the name as the parameter will hold an integer value.

  • If the conversion to an integer does not work then it tries the same with double.

  • If that is also not feasible then it will check if the actual value is lower case true or false. In this case the global variable of the script will be a Boolean value.

  • In any other case, the global variable will get the actual value as a string assigned to it.

The actual scripting implementation may not have Integer, Double or Boolean type but there will be some script type corresponding.

The following sample shows a simple script that implements a looping construct using JavaScript. The source Jamal file:

{@script for(loopvar,start,end,text)=
    var c = "";
    for( var i = start ; i <= end ; i++ ){
        c = c + text.replaceAll(loopvar, ""+i);
    }
    System.out.print(c);
}
{for%xxx%1%3%xxx. iterated
}

The output generated by the Jamal preprocessor:

1. iterated
2. iterated
3. iterated

Note that the JavaScript code itself contains the macro opening and closing strings. This does not do any harm so long as long these are in pairs. It is a better practice to change the separator characters to something that cannot appear in the body of the script macro.

Starting with version 1.3.0 Jamal support the JShell built-in scripting engine. You can define JShell as script type. In this case the content will be passed to the Java built-in JShell engine. When the script is invoked the result of the macro will be the string that is printed by the JShell script. If this is empty then the value of the last Java shell snippet will be used. The argument names have to be valid Java identifiers. When the script is invoked they will be defined as String, long, double or boolean variables. They will get the actual values of the parameters. The type depends on the actual value. If the value string can be interpreted as a long then it will be converted to long. If the string is not a long, but can be converted to double then the variable will be double. If the string is either true or false case insensitive then the variable will be boolean. In any other case the variable will be declared as String.

In short, the arguments to a script macro will be converted to the following types in this order, whichever first succeeds:

  • int

  • double

  • boolean

  • String

For more information and details see the section xiii. JShell

xiii. JShell

since 1.3.0 (core)

The Java built-in scripting engine JShell can be used to define macros. The macro script and the macro JShell can be used to define JShell scripts.

The macro JShell can be used to define methods, classes, variables and so on. The macro script is to define a script macro that later can be invoked like any other used defined script macro.

When the macro JShell or script is used the result is empty string. When the script is invoked the output of the macro will be what the script prints out to the standard output.

The following example defines a global method, a script using the method and then it invokes the script.

{@JShell
    void hello(){
        System.out.println("Hello, " + world);
    }
}{@script hello/JShell(world)=hello();}
{hello My Dear}

It simply prints

Hello, My Dear

The macro JShell defines the method hello(). The macro script is a script macro that has one argument. Note that this argument is also the name of the global variable world. This global variable is used in the JShell snippet defined above but this is not an argument to the method. When we use the line

{hello My Dear}

Jamal will invoke the JShell interpreter executing

String world = "My Dear";

first, and then

hello();

Since the method hello() prints out to the standard output Hello, My Dear this is the result of this macro.

If there is some error in the code of the snippet then Jamal will throw a BadSyntax exception. In this exception the causing exception is included if there is any. This causing exception should give some clue to find out what the issue is. If that does not help then using the interactive JShell program should help.

Creating a JShell execution environment is expensive. To do that the Java starts a new JVM process for the JShell. Many Jamal macro processing do not need the extra JShell. It would slow down Jamal if we created the JShell process for each and every processor even when it is not needed. The JShell environment is created only when it is unavoidable. It is when the processing uses the first time a JShell type script. It not when the script is defined. It is when the defined script is used. In the above example the JShell interpreter is created when the {hello …​} macro is evaluated. Only at that point all the prior definitions that were defined in any {@JShell } macro are fed into the JShell interpreter.

The consequence is that you do not need to worry about the performance when you design a macro library. The processed files can bravely import the macros even if they declare JShell usage. It will not slow down the processing creating a JShell engine, only when the JShell engine is needed.

Another important side effect of this optimization is that you will not get an error message for an erroneous {@JShell } macro until the JShell interpreter is used. When you design a macro library it is not enough to import the library to discover possible errors in the JShell scrips. The scripts have to be used to manifest the error.

xiv. for

  • since 1.0.0 (core)

  • since 1.5.0 multi-argument for

  • since 1.6.3 backtick string separator value list

  • since 1.7.3 options between [ and ]

  • since 1.7.8 option evalist

The macro for can be used to repeat the same text many times. This macro has two forms. The syntax of the macro is either

{@for variable in (a,b,c,d)= content to be repeated
containing variable}

or

{@for (v1,v2,v3) in (a|w|1,b|q|2,c|r|5,d|t|9)= content to be repeated
containing v1 v2 and v3}

The variable or the multiple variables can be used in the content and will be replaced for each iteration with the respective element on the comma-separated list. When there are multiple variables then the sub-list of the values is separated using the | character. Both the command and the | character can be modified to use something else instead of these characters.

The list of the values can also be separated by other strings. If the macro $forsep is defined, like in

{@define $forsep=\s+}

then the arguments will be separated by one or more spaces. The string between the ( and the ) will be split using the string defined in $forsep as a regular expression.

Similarly, if the macro $forsubsep is defined, like in

{@define $forsubsep=:}

then the values for the different variables will be separated by a semicolon.

Note that the macros $forsep and $forsubsep can also be defined inside the for macro body in case the macro is used with the # character at the start. In this case the definition of these macros is limited to the evaluation of this very for macro.

Starting with version 1.7.3 you can also define these options locally using the format

{@for [options] x in (a,b,c)=...}

where the options can be

  • $forsep, separator to specify the separator regular expression

  • $forsubsep, subseparator to specify the sub separator regular expression

  • trimForValues, trim to trim off the sapces from the values

  • skipForEmpty, skipEmpty to skip empty parameter list (see below)

  • lenient for lenient operation (see below)

  • evaluateValueList, or evalist to instruct the loop that the list of the values between the ( and ) has to be evaluated.

For example the macros:

{#for $a in (a:b:c)={@define $forsep=:}a is $a
}{?$forsep}

will result

a is a
a is b
a is c

In this case the value of the macro $forsep is effective inside the for, but it is undefined outside. Another example:

{#for {@options trimForValues}{@define $forsep=:} $a in ( a : b :c )=a is $a
}
{@for [trim separator=":"] $a in ( a : b :c )=a is $a
}

will result the same output:

a is a
a is b
a is c

a is a
a is b
a is c
Note

Using the # character in front of a built-in macro in the first version instead of @ will make the content evaluated before the macro. The content evaluates in a freshly opened scope, which is usually closed before the built-in macro evaluation. It means that any local definitions inside the macro use go out of scope when the built-in macro evaluates.

However, some built-in macros, like for, rely on the macros' value defined inside. We call these built-in macros "inner scope dependent" macros because they depend on the inner scope. If you look at the Java implementation of such macros, you can see that they implement the interface InnerScopeDependent. If a built-in macro is inner scope dependent, it evaluates while the internal scope is still open. In this case, the scope closes after the built-in macro evaluation finishes.

If the built-in macro is surrounded with an {@eval …​} macro, that is already evaluated in the outer scope. Using the {#!macro …​} way, where the ! character directs Jamal to execute the macro’s result is the same as using the {@eval…​} surrounding the macro. The "post evaluation" runs in the outer scope.

The macro for is inner scope dependent.

Also, the second example shows that the same effect can also be reached using the macro options. Macro options are always between [ and ] characters in case of the core built-in macros. The ( and ) characters in case of extension package macros. Some extension package macros use the first line of the macro content to fetch parameters. Note, however that the use of the ( and ) characters to enclose options is only a convention. A 3rd party macro can decide any time to use any character pair as they like.

The number of the actual values separated by | character should be the same as the number of the variables in the for loop. If this is not the case then the macro evaluation will throw a bad syntax exception. This can be suppressed with the option lenient. If the option lenient is used then extra values are ignored and missing values are presented as empty strings. Note that this same option controls how user defined macro arguments are paired to the parameters.

Starting with version 1.5.3 you can fine tune how a for loop treats the empty elements. By default, the empty elements in a for loop value list represent empty strings. The loop body will be rendered with these values replacing the loop variable with an empty string. In a situation like that the use of the option lenient is also a must if the loop has multiple variables. In that case the empty value will be split into a one, empty string value for the empty value in the loop and this has to be assigned to the multipled loop variables. For example

{#for (k,z) in ()=wukz}

will not work, because the empty string cannot be split into two strings (it results one empty string when it is split). On the other hand the following code will work

{#for (k,z) in ()=wukz{@options lenient}}
{@for [lenient] (k,z) in ()=wukz}

and it will result

wu
wu

as both k and z are empty strings. Here, you can see two versions. The first one is declaring the lenient option inside the for macro, the second one is using the option, which is more coincise and shorter.

This default behaviour can be altered using the option skipForEmpty. If this option is used the for loop will skip the empty values. The previous example with this option:

{#for (k,z) in ()=wukz{@options skipForEmpty}}\
{@for [skipEmpty] (k,z) in ()=wukz}\

will evaluate to an empty string. Also note that in this case there is no need to use the option lenient. That is because the empty value is skipped and there is no issue splitting it up into a less number of values than the number of the loop variables.

The example above contains one loop value and that loop value is an empty string. There can be more than one empty values in a for loop and empty and non-empty values can be mixed. The option skipForEmpty and the alias skipEmpty works in any of those cases. For example:

{#for k in (,)=wuk{@options skipForEmpty}}\
{@for [skipEmpty] k in (,)=wuk}

will also result an empty string and

{#for k in (,k)=wuk{@options skipForEmpty}}

will result

wuk

Sometimes the values for the for loop come from some macro. In that case the for macro should start with the # character, otherwise the macro will not be evaluated to the list of values. For example:

{@define list=x,y,z}{@for z in ({list})={@define z=zz}}{?x}{?y}{?z}

will result

{@define {list}={list}{list}}

That is because the content of the macro for is not evaluated before the for loop is executed because we used the @ character. The result of the for loop is not evaluated. We will have to attend to that, but first we have to solve the issue that the macro list is not evaluated. To do that we need to use the # character in front of the for loop.

{@define list=x,y,z}{#for z in ({list})={@define z=zz}}{?x}{?y}{?z}

will result an empty string:

The reason is that the content of the for macro is evaluated before executing the macro itself. That way the macro reference {list} will become x,y,z, but the same time the part, which is after the = is also evaluated. The evaluation will define the macro z to be zz, but this macro is within the scope of the for macro. As soon as the for macro execution is finished the definition of z is lost. What we want is to protect the body of the for macro from evaluation before for the macro is executed and we want it to execute after.

{@define list=x,y,z}{!#for z in ({list})={@ident {@define z=zz}}}{?x}{?y}{?z}

will result

xxyyzz

The macro {@ident …​} is evaluated and its result is the content of the macro and it is not evaluated further before the evaluation of the macro for. The macro for gets evaluated and then the output is evaluated because the macro is preceeded with the back-tick character, which is a shorthand for the core built-in macro eval. This evaluation defines x, y and z.

Because the case that we want to evaluate the list part of the for loop but not the body part is so common there is an option that helps with this. The option evaluateValueList (alias evalist) instructs the macro for to evaluate the value list before iterating through it.

{@define list=x,y,z}{!@for [evaluateValueList] z in ({list})={@define z=zz}}{?x}{?y}{?z}

will result

xxyyzz

We still need the ! character in front of the for but we could get rid of the ident macro and the extra level of nesting.

Sometimes you may need to do a for loop over values that contain the ) character. With the conventional form of the for macro it was not possible, because the first ) character terminates the list of the values. Jamal 1.6.3 introduced a new, backward compatible format for the for macro.

Instead of the ( and ) characters it is possible to use an arbitrary string to denote the end of the values. When the first character after the keyword in (after optional spaces) is the backtick character then the string till the next backtick character will be used to denote the end of the values. The starting and ending backtick should also be part of the string closing the values.

For example the following

{@for x in `END`a),b),c),d)`END`=x }

will result

a) b) c) d)

Note that this alternative format can only be used for the values list and not for the variables. The variables of the for loop should always be listed between ( and ) characters.

xv. if

The if macro makes it possible to evaluate the content conditionally. The syntax of the macro is:

{#if [options]/test/then content/else content}

Here we use / as a separator character but this is not hardwired. The if macro uses the Standard Built-In Macro Argument Splitting to parse the body of the macro.

The result of the evaluated macro will be the then content when the test is true, and the else content otherwise.

When no options are specified the test is true, if

  • it is the literal string true (case-insensitive),

  • it is a signed or unsigned integer number, and the value is not zero,

  • it is any other string that contains at least one non-space character, except

  • when the test is the literal string false (case-insensitive).

The literal false is false using any combination of upper and lower case letters with or without surrounding spaces.

The evaluation of the test string can be modified using options. There are 8 options.

The first three options are "boolean" options. It is enough to use their keyword between the [ and ]. (See examples later.)

  • blank will test true if the test string is blank, it is empty or contains spaces only.

  • empty will test true if the test string is zero length and does not contain even spaces.

  • not will negate the test result.

  • or can be used with the numerical options when more than one test is needed. When you specify more than one equals, lessThan, or greaterThan option the test is true if any of the tests is true. This is the default behaviour, so this option is not needed. Setting or=false has no effect and is not the same as using the option and. This option is included only to add readability if needed.

  • and after the evaluation of multiple tests. When this option is used the test is true if all of the tests are true. This option cannot be used together with the option or and is also needs multiple numeric options.

Note that the options and and or are simple boolean options. They can appear only once in the list of the macro options. You cannot write {@if [equals=3 or equals=4 or equals=6] /9/a/b}. It is recommended to use the or or the and option following the first numeric option. For example, {@if [lessThan=7 and greaterThan=2] /6/it is in (2,7)/out of range}`. If you feel it is more readable you can put these options at any place in the list.

The following options will do numerical comparison. When any of them are used then the test string is converted to a number. If the test string is not a number an error will happen. These options are integer options, which means that you have to specify a number following them like lessThan=55.

  • lessThan (aliases less,smaller, smallerThan) is true if the number is less than the value.

  • greaterThan (aliases greater, bigger, biggerThan, larger, largerThan) is true if the number is greater than the value.

  • equals (aliases equal, equalsTo, equalTo) is true if the number is equal to the value.

There is no separately "less than or equal" and "greater or equal" option. If, for example, you want to test that a number is greater than or equal to a certain value then you can use the greaterThan and the equals options together. An alternative is to use the lessThan option along with the boolean not option.

Note

The option blank is needed in case you have a special case when the literal false should be treated positively. The need for this option arose when we wanted to create a macro supporting XML documents. The default macro generated <tag> from {tag} when the tag was not defined. At the same time {tag something} was converted to <tag>something</tag>. The two different cases were separated using an if macro. The definition was something like this:

{@define default($_,...$x)={#if
    `/SEPARATOR/`$x/SEPARATOR/<$_>$x</$_>/SEPARATOR/<$_>}}\
{tag false}

The only problem was that the if macro was not able to handle the case {tag false}. In that case the evaluation results

<tag>

instead of <tag>false</tag>.

To fix that we need to use the option blank as in the following sample:

{@define default($_,...$x)={#if [not blank]
           `/SEPARATOR/`$x/SEPARATOR/<$_>$x</$_>/SEPARATOR/<$_>}}\
{tag false}

This will result the desired

<tag>false</tag>

The following examples show a few cases, as demonstrations:

{@if /1/true/false}=true non-zero integer
{@if /true/true/false}=true literal true
{@if /0/true/false}=false zero integer
{@if ::true:false}=false condition is empty string
{@if :false:true:false}=false literal false
{@if :FaLSe:true:false}=false literal false
{@if :avraka kedabra:true:false}=true condition is non-empty string
{@if/0/anything can come here}= 'else' part is missing, output is empty
{@if/+1/true}=true  non-zero integer
{@if/-1/true}=true  non-zero integer
{@if/0.000/true}=true  non-empty string, floating points don't work
{@if [not blank]/false/true/false}=true true because 'false' is not blank
{@if [not empty]/false/true/false}=true true because 'false' is not empty
{@if [not]/1/true/false}=false the option 'not' can be used solitary
{@if /  /true/false}=false spaces mean false by default, but
{@if [not empty]/  /true/false}=true not empty
{@if [not blank]/  /true/false}=false is the same in this case as no option
{@if [empty]/  /true/false}=false no, not empty
{@if [not]/  /true/false}=true blank is false by default, but it is negated
{@if [blank]/  /true/false}=true is the same as the above in this case
{@if [lessThan=13]/12/true/false}=true 12 is really less than 13
{@if [lessThan=13]/13/true/false}=false 13 is not less than 13
{@if [lessThan=13 equals=13]/13/true/false}=true 13 is less than or equals 13, note the value twice
{@if [greaterThan=13 not]/13/true/false}=true 13 is not greater than 13, it is the same logic as the previous
{@if [lessThan=13 equals=14]/13/true/false}=false 13 is not less than 13 and does not equal 14
{@if [lessThan=13 and largerThan=2]/12/true/false}=true 12 is in the range (2,13)
Note

The above example is generated running the samples. The composition of the sample is somewhat complex. It uses sophisticated macros that heavily use the macro evaluation order. These macros also check that the 'if' macro really works the way it is supposed to. If you are interested in how it looks, check the file README.adoc.jam and search for the string "avraka kedabra". This string, avraka kedabra appears only once in the Jamal source file.

xvi. ident

since 1.0.0 (core)

ident is an extremely simple macro. It just returns the body of the macro. The name stands for identity. It is helpful in some complex cases when you need to fine-tune the macro evaluation order. It is the case when Jamal should not evaluate some macro while it should others in a local scope. For example:

{@define b=92}{#define c={@ident {a}}{b}}{@define a=14}{c}

When we define the macro c we do not want to evaluate {a}. There are two reasons for this. One is that at that point, a is not defined. The other is to use the actual definition of a whenever the macro c is used. On the other hand, we want to evaluate b. This way, c will become {a}92. When later c is used, and we already defined a as 14, then the final result will be 1492.

1492

Note that c is defined using the # character before define. At the same time, we used @ in front of ident. Jamal evaluates the content of define. In this evaluation {@ident {a}} is evaluated and {b} is also evaluated. {@ident {a}} becomes {a}. {b} becomes '92`. This way c will become {a}92.

If we redefine later a to some different value then c will follow this change. If we redefine b the value of c will still remain 1492 assuming a is still 14.

You can also use this macro to enclose some text into a block where the definitions are local. For example, you may want to modify the macro start and end strings temporarily. In that case, you can use the sep macro at the start and use the sep macro without argument to reset the previous value. You can also enclose the setting of the macro start and end string into an ident block.

Specific use of ident is to insert a "null length separator" into the text. Imagine that the macro start and close strings should be and . We may want to use those because the curly braces are used in the text frequently, and so are the single ( and ) characters.

Note

Generally, it is not a good idea to use opening and closing strings that contain repeated characters. The reason for this is precisely the situation we describe in the example below. It isn’t easy to read the closing strings when there is more than one. For example, how many ))))))) double closing )) are there in this string? In the example, we use these strings to demonstrate how you can deal with a situation like that in case you have to. The possibility shows Jamal’s power, but it does not mean that you should utilize all these tricks. It is better to choose better opening and closing strings if the default { and } do not work. Many times {% and %} are good choices. The source of this document also uses these opening and closing strings. I used the ident macro between the characters of these strings to have them in the output.

As an example, we may want to define a macro that creates a markdown image reference:

((@define image($ref)=![](images/$ref.png) ))

This example needs a space after the closing ) character at the end of the image url. If we did not have this space, the macro would be closed one ) sooner than needed. To avoid that, we insert an extra space after the image reference. Usually, it is not a problem. In some situations, however, we do not want to have that extra space there. It is possible using ident.

((@define image($ref)=![](images/$ref.png)((@ident))))

The macro @ident will prevent Jamal from interpreting the ) character after the .png as the first character of a macro closing string. At the same time @ident produces no character, not even a space in the output. You can also use the macros comment and block the same way.

Be aware that the macro ident consumes the white spaces (including newlines) that follow the ident keyword. It is to avoid extra white spaces when tabulation gives better readability. If you need the whitespace (e.g., newline) in the output, you can put those in the ident macro.

Starting with Jama 1.5.0, there is a built-in language syntax similar to ident. If a macro is preceded with a

`

backtick character, then the macro will not be evaluated. The above example, which is

{@define b=92}{#define c={@ident {a}}{b}}{@define a=14}{c}

can also be written as:

{@define b=92}{#define c={`a}{b}}{@define a=14}{c}

This built-in "ident" can be used many times if you want to postpone the macro evaluation multiple times. You can have

{``c}

or

{``````c}

as many times as it makes sense. You can use this macro modification character together with the ! character. There is no restriction on ordering the ! and the backtick characters in case they are used together. If you use many of them in extreme cases, you can mix them. Note, if the macro does not get evaluated fully, Jamal may not preserve the order of these characters in the output.

xvii. verbatim

since 1.0.0 (core)

verbatim is a special macro, which affects macro evaluation order and is used for advanced macro evaluation. To understand what it does, we have to discuss first how Jamal evaluates the different macros.

Jamal parses the input from the start towards the end and copies the characters from the input to the output. Whenever, when it sees a macro then it evaluates the macro, and the result of the evaluation is copied to the output. This evaluation is done in three steps, two of those are recursive. Let’s have a simple example:

{@define a=this is it}{@define b={a}}{#define c={b}}{c}

The macro a is defined simply. It is this is it. Whenever a is evaluated it will result the string this is it.

The macro b has the value {a}. When macro b is defined the content {a} is not evaluated before the definition because there is a @ before the define. When b is evaluated it results {a} and then before using this output in place of the use of the macro b this result is evaluated by Jamal as a new input. This second recursive evaluation will result in the string this is it.

The macro c is defined using the # character before the keyword define, therefore Jamal will process the body of the macro before processing the built-in macro define itself. Essentially, it will evaluate {b} first. It will put the resulting characters after the = sign in the definition of c and then it will evaluate the define built-in macro.

As we discussed above when this time {b} is evaluated it results {a}, which also gets evaluated and then it results this is it. Therefore, the value of the macro c is this is it and that is what we see in the output:

this is it

This way the evaluation of a macro is done in three steps:

  1. Evaluate the body of the macro unless the macro is built-in and starts with the character @. For this evaluation Jamal starts a new scope and evaluate the macros following these three steps.

  2. Evaluate the macro itself. If it is a built-in macro then it calls the evaluate() method of the Java class that implements the macro. If the macro is user defined then it evaluates as described in the section define.

  3. If the macro is user-defined or starts with a ! character then Jamal evaluates the output of the macro. If it contains macros then evaluate those using these three steps.

As you can see the first, and the last steps are recursive steps. The first step can be skipped using the @ character, but only in case of built-in macros. The second step cannot be skipped, and after all, there is no reason to do so. However, the third step can be

  • skipped using the macro verbatim if the macro is user defined, or

  • enforced using a ! in front of the @ or # character if the macro is built-in.

The use of the ! character in front of a built-in macro is similar to the use of the macro eval. For example

{@define tag(_x)={@define _x(_y)=<_x>_y</_x>}}
{#eval {@for _tag in (groupId,artifactId,version)=
{tag/_tag}}}

can be shortened as

{@define tag(_x)={@define _x(_y)=<_x>_y</_x>}}
{!@for _tag in (groupId,artifactId,version)=
{tag/_tag}}

The only difference is that the eval macro consumes the white-space characters at the start of its argument. In the example above the {#eval macro …​} before its evaluation is

{#eval
{@define groupId(_y)=<groupId></groupId>}
{@define artifactId(_y)=<artifactId></artifactId>}
{@define version(_y)=<version></version>}}

The body starts with a new line. The macro eval deletes this new line, while using the ! in front of the macro does not.

The syntax of the verbatim macro is the following:

{@verbatim userDefinedMacroUse}

The verbatim macro has to be followed by a user defined macro use. If we modify the previous example to use verbatim we can do it the following way:

{@define a=this is it}{@define b={a}}{#define c={@verbatim b}}{c} {@verbatim c}

In this example {@verbatim b} is the same as {b} in the previous example. The only exception is that after b is evaluated the result is not processed further for macros. It is used directly as the value of the new macro c because of the verbatim keyword. The value of c will be {a}. Also, when we use {c} the result of c is scanned as a third step for further macros. In this case, there is one because the value of the macro c is {a}, that further evaluates to this is it. On the other hand when we use {@verbatim c} then the result {a} is not processed any further.

this is it {a}

Note that the macro verbatim is a special one because it is hardwired into the evaluation logic of Jamal and it is not a "real" built-in macro. In other words, if there are user-defined macros and built-in macros then verbatim is one level deeper built-in than the other built-in macros. To understand this may be important if you want to write your own built-in macros as Java classes. You cannot "redefine" verbatim.

You cannot use verbatim together with the ! macro modifying character. Their meaning is exactly opposite.

Fine points of macro evaluation
Note
This section does not apply to any version prior 1.2.0

Recall the three steps of macro evaluation:

  1. Evaluate the body of the macro unless the macro is built-in and starts with the character @. For this evaluation Jamal starts a new scope and evaluate the macros following these three steps.

  2. Evaluate the macro itself. If it is a built-in macro then it calls the evaluate() method of the Java class that implements the macro. If the macro is user defined then it evaluates as described in the section define.

  3. If the macro is user-defined or starts with a ! character then Jamal evaluates the output of the macro. If it contains macros then evaluate those using these three steps.

These points can be refined further:

  1. First the beginning of the macro text is evaluated if the text contains macros. The user-defined macro name itself in the text can be the result of another macro. For example, calling the macro named white can be {white}. If there is another macro {@define black=white} then using {{black}} will result the same as {white}. In this case first {black} is evaluated to white and then {white} is evaluated. There may be multiple macros at the start. For example, we can have {@define bla=whi} and {@define ck=te}. Using these we can get {{bla}{ck}} to {white}.

  2. The second step is that the content of the macro is split up into the macro name and the parameters. Recall that the first character that is not part of the name of the macro is used as a parameter separator character. This is a non-space character that cannot be part of a macro name, or the first character that follows the spaces after the macro name. The splitting process takes care of the macro calls that are in the arguments. For example the macro {q/a/{b|c/g}} will get two parameters. The first parameter to q is a, the second is {b|c/g}. The first / character separates the name of the macro from the parameters. At the same time, it defines which character is used as a separator character. The second / character separates the first and second parameters. The third / is not used as a separator character because it is inside a macro use. This character is not used as a separator character, even when the macro {b|c/g} is evaluated, because in that macro use the separator character is |. Similarly, if we look at the macro {q/a/{b/c}} then the parameters are a and {b/c}. In this case, the third / is ignored and is not considered as a parameter separator. Although this character is a parameter separator when the macro b is evaluated. The characters that are inside further macro calls are not used as parameter separators.

  3. When the parameter strings are identified then they are evaluated one after the other. In the previous example a and {b|c/g} are evaluted before q is evaluated. When the macro q is evaluated, the parameters already contain the result of the evaluation of these macro uses.

The versions of Jamal prior 1.2.0 (so up to and including 1.1.0) evaluated user-defined macros simpler. In those versions the body of the macro was evaluated as a whole in one simple step. The parameter separator character was used in a very simple splitting operation. Those versions did not check if the separation character was inside an embedded macro use.

That way it may have happened that some macro was evaluated, and the resulting string contained the separator character. This is usually not what the users intend, and creates a bug that is hard to find. In the previous examples the evaluation of the macro use {q/a/{b/c}} would evaluate first a/{b/c}. After that, the splitting takes place on the resulting string.

Usually, this results in the same as the new algorithm. However, if the definition of b is for example {@define b(Z)=shoot/Z}, then the evaluated string will be a/shoot/c. In this case the final evaluation will get (prior 1.2.0) {q/a/shoot/c}. It will result in three parameters. This is probably an error because q in the example needs only two. Even, if the option lenient was declared the result is not the one the author of the text expected.

The versions 1.2.0 and later can revert to the earlier algorithm if the Jamal code defines the option omasalgotm. Using the macro options as {@options omasalgotm} you can switch to the old algorithm. The name of the option is an abbreviation and is hard to remember to distract from the use of it. If you need this option then your Jamal source file does some shady thing that it should not. This option is obsolete from the very start of the introduction and is meant as a last resort to keep backward compatibility. It is removed from Jamal versions 1.10.0 and later.

xviii. sep

since 1.0.0 (core)

This macro can be used to change the macro opening and closing string. In the examples, in this documentation, we use { as the opening string and } as the closing string. Jamal itself does not impose any such predefined setting.

The syntax of the command is

{@sep /startString/endString}

If both the start and end strings are a single character, for example [ and ] then you can use the simple form:

{@sep []}

A two-character argument to the macro sep will use the first character as macro opening string and the second as macro closing string. You can also use three character. For example:

{@sep [.]}

The separating character between the opening and closing string characters can be any character except any of the opening or closing string character. It is also possible to use the format

{@sep openingString  \s+   closingString}

separating the opening and closing strings with spaces. This format is very readable and convenient in many cases. For example, you can specify

{@sep (( )) }
{@sep ([ ]) }

and other, similar opening and closing strings. There are some definitions that can be misleading. For example, the following declarations can be interpreted by humans in multiple ways.

{@sep/[/ ] }   <- is it "/[/" and "]" or "[" and "]"
{@sep/[ /]}    <- is it "/[" and "/]" or "[" and "]"

Many human readers would tend to think the second. The syntax however matches the \S+\s+\S+ pattern. To avoid any such ambiguous situation Jamal does not allow the use of this form when

  • the opening string

  • starts and ends with the same character

  • is at least three characters long, and

  • it does not contain the first character inside

or

  • the closing string

  • starts with the same character as the opening string

  • at least two character long

  • does not contain this character after the first character.

These seem to be complex rules. They contain a bit of heuristics. They were designed to let the users use the most readable format of the sep macro. The same time they help avoid unreadable declarations and errors.

If in doubt then you can always use the last, definitive syntax that does not rely on any heuristics. This syntax is described in the followings.

If the syntax does not match and of the previous cases, Jamal will use the syntax that is defined with the following "regular expression" like line:

{@sep \s* (\S) opening_string (\1) closing_string \s*}

There can be whitespace characters after the macro name sep, and at the end, but these are optional. The first non-space character is used as a separator character that separates the macro opening string from the macro closing string. It is usually the / character, but it can be anything that does not appear in the opening string. Prior to 1.3.0 this character could appear in the closing string, although it is not recommended. Starting with 1.3.0 it is an error. It is possible to use spaces inside the macro opening and closing strings, but it is not recommended. Leading and trailing spaces of the opening and closing strings will be trimmed off. That way

{@sep /[[/]]}
{@sep /[[ / ]]}
{@sep /[[ / ]] }
{@sep / [[ / ]] }

are all the same. Note though that {@sep / [[ /]]} would be logical in the above list, but it is missing. There is only one space (\s+) separator between the / and /]] strings, and it matches the

{@sep openingString  \s+   closingString}

format, and it will set the separators to / [[ and /]].

Note that the macro sep should be terminated with the original macro closing string. The macros after it already have to use the altered opening and closing strings. This makes it a bit tricky when you want to use a closing string that happens to contain the original closing string. Assume that the current opening string is { and the current closing string is }. You want to have {{ as an opening string and }} as a closing string. This is often the choice when using Jamal in a programming language environment that heavily uses { and`}` braces. In this case

{@sep/{{/}}}

will not work. It will set the closing string empty which is not valid and will raise an error. To overcome the situation you have to change the separator strings in two steps:

{@sep/[/]}[@sep/{{/}}]

Also, do not forget that the end you should call sep without an argument twice:

{{@sep}}[@sep]

unless you want this change till the end of the scope.

The change of the opening and the closing strings always happens in pairs. You cannot change only the closing or only the opening string. You can, however, redefined one of them to be something that is different from the current value, and the other one to be the same as the current value. To do that you will need two steps for the reason described above. Even in this case, the definitions should specify both strings.

The change of the opening and closing strings is valid only for the current scope. Returning from the scope the original value is restored even if the strings were set to different values multiple times.

Neither the opening nor the closing string can be empty. Trying to set it to an empty string will raise an error.

Note
  • Jamal 1.0.0 got into an infinite loop in case of an empty opening string. Later versions will signal an error.

  • Jamal 1.3.0 extended the sep macro.

When the opening and the closing strings are set, the original values are stored in a list. It is possible to use the macro sep without any separator string specification. In this case the macro call is nothing more than the macro name, like {@sep}. In this case the last opening and closing strings are restored. The strings are stored in a stack, so you can define new strings and return to the previous one many times nesting the redefinitions.

The following sample is executed with { and } as opening and closing string at the beginning. After that, it sets the strings to [[ and ]]. This is used to define the macro apple. After this when the scope of the next macro, comment starts the opening and closing strings are still [[ and ]]. Starting a new scope does not change the macro opening and closing strings.

It would be an error to use [[@sep]] inside the scope of the macro comment at this point trying to restore the original macro opening and closing strings. In that scope at the start, there are no opening and closing strings to be restored. The opening and closing strings do not belong to this scope, they are simply inherited from the outer scope. On the other hand, the sample can change the strings, as it does to << and >>. Using these it defines the macro z. Note that z is not exported from this scope.

After that the <<@sep>> restores the opening and closing strings to the inherited one and with these, it defines a1 and a2 and exports them. Note, that a1 will have the actual value of the macro z evaluated inside the scope of the comment macro. The macro a2 starts with @ thus the body is not parsed during the macro definition and thus the value of a2 is unevaluated, as it is. Similarly, the macro a3 will have the value`{z}`.

All these macros are evaluated because the macro comment is started with the character #. It means that Jamal will evaluate the body of the macro before evaluating the macro itself.

After the comment macro the separators are set back to the original value { and } automatically. Then we have a simple macro definition that defines z and then this z is used, and the exported a1, a2, and a3.

z is now, as defined in the outer scope is SSS. a1 has the value that came from the macro z as it was defined inside the scope of the macro comment. Macro a2 has the value that has nothing special in the current scope. The macro a3 has the value {z} which is evaluated after the macro a3 is replaced with its value.

{@sep/[[/]]}
[[@define apple=fruit]]
[[apple]]
[[#comment [[@sep/<</>>]]
<<@define z=zazi>>
<<#sep>>
[[#define a1=[[z]]]]
[[@define a2=[[z]]]]
[[@define a3={z}]]
[[@export a1,a2,a3]]
]]
[[@sep]]
{@define z=SSS}
{z}{a1}{a2}{a3}{@verbatim a3}
fruit



SSSzaziSSS{z}{@escape `a`{`a`}z{@escape `a`}`a`}

xix. export

since 1.0.0 (core)

export moves the definition of one or more user-defined macros to a higher scope.

The syntax of the macro is

{@export macroname,macroname, ... ,macroname}

exporting one or more macros, comma separated.

When a macro is defined it is defined in the current scope (unless the name contains one or more :, or it starts with :).

The Jamal input file is one scope and if there is a macro defined in the file on the top-level then that macro can be used anywhere inside the file. However, when Jamal includes a file into another it opens a new scope. The macro include should include some text in the output. It can be used, for example, to split up a long document into chapters and then use Jamal to create the final output. In that case, the macros defined in the included files should not interfere with the definitions in the file that includes the other one. To accomplish this separation Jamal starts a new scope when it includes a file. Scopes are embedded into each other like a call stack in a programming languages. When a macro is defined in scope it is available in that scope and all other scopes that are opened from that scope. When a macro is redefined in a scope the redefined value is used until the scope is closed. In the case of an included file, the user-defined macros defined in the included file disappear as soon as the included file processing is finished.

The setting and resetting of the separator characters is also limited to the scope. You cannot reset the separator character to a value that was set in a lower, or higher scope.

Jamal opens a new scope in the following cases:

  • When a file is processed with the include macro.

  • When macros are evaluated inside another macro. This is the case of user-defined macros or in case of built-in macros when they are started with the character #.

  • Other built-in macros that are not part of the core package may also open and close scopes. Built-in macros are provided in form of JAR files.

Note that the macro import does NOT open a new scope to process the imported file. This is because of the aim of import is to have the macros defined in the imported file available in the file that imports them.

In the following example, we define the macro Z in the scope of the macro comment. The {@define Z=13} is evaluated before the comment macro because we use the # in front of the comment macro. When the comment evaluation finishes the scope is closed and Z is not defined anymore. In the second case the macro Z is exported using the export macro. The export macro moves the definition of the macro from the scope of the comment to the enclosing scope.

The example:

A comment starts a new scope {#comment {@define Z=13}} Z {?Z} is not defined here unless...
{#comment {@define Z=14}{@export Z}}Z is exported. In that case Z is {Z}.

will result:

A comment starts a new scope  Z 1 is not defined here unless...
Z is exported. In that case Z is 14.

You cannot export a macro defined in a higher scope. You can use those macros and you can reference them. It is just that you cannot export them to the enclosing scope because they do not belong to the current scope. You can export a macro that was defined in a lower scope and was exported to the current scope. However, you cannot export a macro that was defined in a lower scope but was not exported to the current scope, simply because they do not exist anymore when the export is executed. You cannot export macros from the top-level scope, because there is no enclosing scope above that.

xx. options

since 1.0.3 (core)

The options macro can be used to alter the behavior of Jamal. The options can be listed | separated as an argument to the macro.

{@options lenient|failfast|mySpecialOption}

The macro does not check the option’s name. For example the option lenient is used by Jamal itself and by the for macro, however, if you type {@options lenuent} misspelled the options macro will not recognize it as an error. The option lenuent could be used by some other macros and the options macro just treats it as a new option. It stores the options specified, and they can be queried by any other built-in macros. Any extension can define and use any options it likes.

The scope of the options is local, or global the same way as the scope of user-defined macros.

Note

Technically the options are stored along the user-defined macros. These objects cannot be evaluated only queried for their stored value, which is either true or false. It is possible to export the options to higher layers the same way as macros.

{@define macro($a,$b,$c)=$a is $a, $b is $b{#if :$c:, and $c is $c}}\
{macro :apple:pie:truffle}{@comment if there are three arguments, we handle it}
{macro :apple:pie:}{@comment here we need : at end, default is not lenient}
{#ident {@options lenient}{macro :apple:pie:}}{@comment options is local, but lenient is a global option}
{macro :apple:pie:}{@comment here we must have the trailing : because we still do not have a globally defined option options is local}
{#ident {#ident {@options lenient}{macro :apple:pie:}{@export lenient}}{@comment local but gets exported one level up, still not global}
{macro :apple:pie:}{@comment still not global}}
{macro :apple:pie:}{@comment was not exported to this level, only to inside the outer ident block}
{@options lenient}{@comment now this is on the global level}{macro :apple:pie}{@comment nice and easy, global}
{@options ~lenient}{@comment and we can switch it off}
{macro :apple:pie:}
{@options any|option|can  | go | ~go | no go}

An option can be switched off using the ~ character in front of the options name. There can be no space between the ~ character and the name of the option.

Similar to user defined macros, options containing a : are global. You can define a global value for an option using the : prefix in front of the name of the option. This character will be removed from the name, the same way as it is removed from the name of global user defined macros. If the : is inside the name then it remains part of the name, and it is not possible to have a local definition for the option.

The options implemented currently:

:lenient

In the lenient mode, the number of the arguments to a user-defined macro do not need to be exactly the same as it is defined. If there are fewer values provided then the rest of the arguments will be an empty string in the lenient mode. Similarly, if there are more arguments than needed the extra arguments will be ignored. The option lenient is global. Nothing will stop you to redefined the option in a local scope, but macro evaluation will use the global value even in that scope.

The lenient mode also applies to the multi variable for loops. In lenient mode there may be more or less actual values than the number of loop variables.

omasalgotm (since 1.2.0 < 1.10.0)

Jamal 1.2.0 changed a lot from 1.0.0 in the way how macros are evaluated. The version 1.2.0 is safer and more flexible and is compatible with the older versions in most of the cases. There may be some cases when the macros are not compatible with the old version. In this case, it is recommended to alter the macros so that they do not rely on the old evaluation algorithm. In the meantime, it is possible to use the option omasalgotm to force Jamal to the old evaluation style.

Version 1.10.0 and later versions do not implement this option. The code providing compatibility with the old evaluation style is not included in the distribution.

nl (since 1.3.0)
  • 1.3.0 till 1.7.6 introduces the option nl.

  • 1.7.7 removes this option

When this option is in effect then all new-line characters are copied into the output. This was the default and non-changeable behavior prior 1.3.0.

In versions 1.3.0 and later it is possible to escape a newline character that is following a macro closing string. For example the macro {@define z=1} can be followed by a \ character before the newline. That way {@define z=1}\ will tell Jamal that the next newline character is not needed in the output. The backslash, the newline character following it and the spaces that may be between the two will be skipped.

The \ character has to follow the macro closing string immediately, spaces are not allowed. There can be spaces between the \ character and the following new-line character.

{@define z=1}\n          <- new line will get into the output

{@define z=1}\\n         <- the \ and new-line will be skipped, it does not get into the output

{@define z=1}\ ... \n    <- there can be spaces between the \ and the \n, still the
                            \ and new-line characters will be skipped

{@define z=1} ... \\n    <- nothing is skipped, there are spaces before the \ character

A backslash in any other places is just a character and will not escape a newline. This escaping works only following built-in and user defined macros.

Note
Since this is a slight behavioral change in the input processing, therefore it may break some of the source files. We decided to change the default behavior because there is a little chance to have escaped new-line characters in existing jam files. On the other hand, we envision that with the introduction of this feature most of the Jamal source files will use this feature. We wanted to avoid starting every new Jamal source file with the nl option setting.

With the release 1.7.7 this option is not available anymore. The default behavior, skipping new lines after a \ character that follows a macro close string cannot be switched off.

failfast (since 1.7.8)

This option tells the Jamal processor to stop at the first error. With the version 1.7.8 and later the Jamal processor does not stop the processing at the first syntax error. This helps the discovery of all the syntax errors in the input. Prior 1.7.8 Jamal stopped at the first error. The user could fix the error, restart Jamal and repeat this process for each error one by one. The feature introduced in 1.7.8 collects all the errors and displays them at the end of the processing as an aggregate error.

Using this option Jamal 1.7.8 and later revert to the old behaviour.

xxi. try

since 1.5.0 (core)

The macro try will evaluate its content and return the result. The evaluation does not open a new scope, just like in the case of the macro {@eval }. In case the evaluation results an error then the result will be empty string.

For example the following macro will produce an empty string.

{@try {!@verbatim macro}}

The macro macro is not defined, but the error is caught with the macro try.

The macro try can also be used to include the error message into the output. If we use an ! character right after (no spaces) the try keyword the result will be the error message. If there is no error then the result is the result of the evaluated text inside the macro.

Note
Jamal usually allows you to have spaces in places like the keyword try and the following ! or ?. For example you can have spaces between the macro name define and the !, ? or ~ character. In case of try there must be no space. The reason for this strictness is that try is followed by arbitrary text evaluated by the macro itself. Allowing space between the macro name and the ! or ? character would result ambigous syntax. We could not tell if the ! or ? character is part of the macro use or already the first starting character of the text to be evaluated.

If we use a ? character right after (no spaces) the try keyword then the result will be the string true if there was no error and false is there was an error. This can be used to test the "computability" of the text.

The macro try should only be used to debug certain macro files. When an error happens, and the try macro catches the exception thrown the scopes may not be properly closed.

xxii. escape

  • since 1.5.4 (core)

  • since 1.9.1 (escape*)

The built-in macro escape is a special one, like verbatim. It is implemented as Java a macro class, but the macro evaluation process takes the presence of an escape macro into account. The syntax of the macro is the following:

    {@escape `SEP`escaped string`SEP`}

The part SEP between the back-tick characters can be any string, which does not appear inside the escaped string. This string has to be repeated at the end of the macro before the macro end string.

The result of the macro is the escaped string without any modification. The macro itself is very simple. The speciality of macro is that Jamal takes care of this macro when searching for macros in the text. When Jamal sees a macro opening string, then it checks way before the evaluation of the macro if the actual macro is an escape macro or not. It the macro is an escape macro, then it finds the SEP strings, and the macro end string after that. If there is any macro opening or macro closing string inside the escape macro they are ignored.

{@escape `a`{`a`}

will result

{

It is recommended not to use this macro in your Jamal source. If you can solve your task without this macro, then you should do it without such a strong tool.

Note

This macro originally was intended to be used by Jamal itself when evaluating a user defined macro, which was defined using different macro opening and closing strings than the actual one. In this case the macro opening and closing strings, which were in effect at the time of the macro definition are replaced with the current one. That way the macros defined inside the macros will be evaluated even though the macro opening and closing strings have changed.

At the same time the current macro opening and closing strings had no special effect by the time of the macro definition. If there is any current macro opening and closing string in the definition of the macro then they should not play a special role. They get protected using the escape macro automatically.

There is a special way to escape content from macro evaluation. In this special case you can write a * right after the escape keyword, as

{@escape* `a`{`a`}

In this case the escape not only escapes the macro opening and any other otherwise processable content but also results the protecting shell around the escaped string. The result in this case will be

{@escape*`a`{`a`}
Note

You will never see in your output the escape* macro. It is eliminated after the whole file was processed by Jamal invoking a so called closer. The closer object is automatically created and registered by Jamal when the escape* macro is used.

This macro comes in handy when you want to protect something from evaluation that should never be interpreted as a macro text. For example you can have a Maven property in a pom.jam file, like ${project.build.sourceDirectory}. You can redefine at the start of the file the macro open and macro close strings, but it may be simpler to protect the one or few special strings. A normal escape, without the * after the keyword hides the content from evaluation only once. When the * character is used the content will be protected even when it is deep inside macros and target for many different evaluations. It should only be eliminated at the very end of the processing on the top level, which is done automatically.

The macro escape* will liberate the content. The liberation will happen after the whole content was already processed in a so called "closer". Built-in macros implemented in Java have the possibility to register an object to be executed after the processing of the whole content. Such an object is a closer and it can be used to close resources that were open by the macro during processing. During the execution of this closer the code can access and modify the final result.

The macro escape* registers a closer that will invoke the the Jamal processing again for the output with a flag that tells every escape* macro to release its content.

In some cases it may happen that you want the escape* closer run before some other closers, but the escape* happens later than the register of the other closer.

The different closers are invoked in the order they were registered. You may use a macro X, which also creates a closer. The use of the macro X precedes the first evaluation of an escape* macro. In this case the closer registered by X would be evaluated before the escape* closer. If you want the escape* closer to run first, you have to use the macro escape* before X. The simplest form is

{@escape*````}

It essentially escapes an empty string delimited by two backtick delimited empty strings. (Hence the four back ticks.) After the content liberation the result will be an empty string, thus there is no harm using this before any X macro.

The closer registered by escape* works very simple. It simply evaluates the result setting a flag that tells escape* that this time it should ignore the \* character.

xxiii. require

since 1.6.4

Since Jamal has many different versions, there may be a need to stick to a specific version. To do that, the built-in macro introduced in version 1.6.4 require can be used. The syntax of the macro is

{@require [<|>|<=|>=|=] version }

The evaluation of this built-in macro will check that the currently running version of Jamal is

  • less-than

  • greater-than

  • lees-then or equal

  • greater than or equal, or

  • equal

to/than the version specified after the comparison sign. The use of the comparison sign is optional. The default comparison is greater than or equal.

If the comparison fails, the evaluation of the macro will result in an error. If the current version matches the requirement, then the result of the macro is an empty string.

For example:

{@try!{@require 6666.5.3}}

will result

The current version 1.10.4 is older than the required version. It has to be newer.

You can specify only one version in a require macro. If you want to specify a minimum and a maximum version, you should use two require macros.

Using a version before 1.6.4 in the require argument is an error.

4. Standard Built-In Macro Argument Splitting

The built-in macros access their input as one single string. Technically the input parameter of the method evaluate() is not a string but for now we can assume it is something like a string. The Java code of the macro is free to interpret this string the way it wants. Different macros implement different syntax analysis and later their behaviour based on the syntax of the input.

To manage the input and to easy the format analysis and interpretation of the input there is a utility class named InputHandler. This class defines a method named getParts() which does a simple analysis. It splits up the input into an array of strings in a "standard" way.

This method is used, for example, by the implementation of the if built-in macro. When there is no special requirement for a macro it is recommended to use this method. Using this method provides a concise way for macro argument separation. The way it splits the arguments is defined here so that the extension documentations can refer to this section.

The splitting offers three syntax variations:

  1. macroName / a / b / c / …​ /x

  2. macroName a b c …​ x

  3. macroName `regex` separator a separator b separator ... separator x

In the first case the argument separator character is the first special character. This character can be any unicode character except

  • letter or digit,

  • back-tick character,

  • white space character.

If the first non-white space character is a letter or digit character then the second syntax is used. In this case the input is split up along the white space characters. Multiple adjacent white space characters are counted as one. The splitting does not create empty parameters.

The third possibility is when the fist non-space character is backtick (`). If the first non-whitespace character after the name of the macro id is a backtick then the parsing expects to be a regular expression till the next backtick. After the regular expression and after the closing backtick the rest of the input is spit up using the regular expression as separator.

Backtick was selected during the design of the syntax to enclose the regular expression because this character is very rare in Java regular expression. In case you need one inside the regular expression then you have to simply double it, and the parsing will single it back.

5. Standard Built-In Parameter Parsing

In addition to the method getParts() there is another utility that the built-in macros can use. It is the class Params. The class is a utility to parse some particular part of the whole input of the built-in macro looking for parameters. This particular part is usually the first line of the input, but it can be the part between ( and ) following the macro ID or the whole input. This utility is used by some of the built-in core macros. The core macros use the [ and ] characters to enclose the parameters.

Note

The core macros cannot use the ( and ) characters, because the syntax of the macro for already supported the multi-variable version of it. Because of that options between ( and ) could not be distinguished from the variable list. To be consistent the macros include, import, eval, if, and defer also use the [ and the ] characters. This is also a clear visual separation of core macros from other macros provided by extra modules.

The documentation of the parameter handling in these macros is not part of this readme. It can be found in the separate PARAMS document.

6. Jamal Environment Variables

You do not need to configure Jamal. The environment variables that you may set to modify the behavior of Jamal are documented in this section. All environment variables start with the prefix “JAMAL_”. For every environment variable, there is a corresponding system property. The name of the system property is the same as the environment variable lower case converted and replacing the _ to . characters. For example, for the environment variable JAMAL_CHECKSTATE, the system property is jamal.checkstate. First, the system property is consulted, and the environment variable has only effect when the system property is not defined. The following sections describe the individual environment variables.

JAMAL_CONNECT_TIMEOUT

This variable can define the connection timeout value for the web download in millisecond as unit.

The default value for the timeouts is 5000, meaning five seconds.

The proxy setting can be configured using standard Java system properties. For more information see the JavaDoc documentation of the class java.net.HttpURLConnection in the JDK documentation.

JAMAL_READ_TIMEOUT

This variable can define the read timeout value for the web download in millisecond as unit.

The default value for the timeouts is 5000, meaning five seconds.

JAMAL_TRACE

This environment variable defines the name of the trace file. When a trace file is defined the evaluation and all the partial evaluations are appended to this file during processing. This file can grow very fast, and it is not purged or deleted by Jamal.

JAMAL_STACK_LIMIT

sets the recursive call depth in macro evaluation. Macros may be recursive and in some cases it may create infinite recursive calls in Jamal. Try a simple Jamal file that contains {@define a={a}}{a}. This will drive Jama into an infinite recursive call. During the macro evaluation {a} will result {a} again and this will be evaluated again and again. Infinite recursive calls result StackOverflowError which should not be caught by any program. To avoid this Jamal limits the recursive calls to the maximum depth 1000. This is a reasonable limit.

  • Most Jamal sources are not complex, and will not get above this limit recursively.

  • At the same time, most Java implementations can handle this dept.

This limit may be too much in your environment. Jamal may still throw StackOverflowError. In this case set this to a smaller value. It may also happen that you deliberately create complex recursive macros. In that case this limit may be too small. Set your value to a limit that fits your need.

JAMAL_CHECKSTATE

This environment variable can switch off macro statefulness checking during macro registration. It is generally recommended that the macros are stateless to support multi-thread evaluation when a single JVM runs multiple Jamal processors in one or more threads. If a macro has to have a state, it must be annotated using the annotation Macro.Stateful. The statelessness or annotation is checked during macro registering since Jamal version 1.8.0. You can switch off the functionality setting this environment variable to false. It may be needed if you want to use an older, prior 1.8.0 library or a library that does not follow this rule.

JAMAL_DEBUG

This environment variable can switch on debugging of Jamal. To use the debugger this variable has to set to a value, which is recognized by a debugger on the classpath. The web based debugger recognizes the http:port format variables. Set this variable to http:8080, put the jamal-debug module on the classpath and after starting Jamal processing open your browser at `http://localhost:8080. The debugger and the use of it is detailed in a separate section.

JAMAL_INCLUDE_DEPTH

This variable can set the maximum number of file include nesting. The default value is 100.

JAMAL_HTTPS_CACHE

JAMAL_DEV_PATH

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.

Only absolute file names can be replaced.

For example, you include the file https://raw.githubusercontent.com/central7/pom/1/pom.jim in your Jamal file. You want to replace it with a local file ~/projects/jamal/pom.jim. In that case you should set the environment variable

export JAMAL_DEV_PATH=\|https://raw.githubusercontent.com/central7/pom/main/pom.jim?SNAPSHOT=~/github/jamal/pom.jim

The environment value is a list of = separated pairs. The list is parsed using the standard InputHandler.getParts(Input) method. This is the reason why the first character in the example is the separator |

JAMAL_OPTIONS

This environment variable can define options for the Jama processor. The value of the variable is interpreted as a multi-part input. The list is parsed using the standard InputHandler.getParts(Input) method. If you just have one option then you can define that with the name. If there are multiple options then you have to select a non-alphanumeric separator character and present it in front of the list.

Note
that the usual | character has a special meaning for the bash, and therefore you may need escaping. Also note that using : as a separator character may work, but it may be misleading as it can also be part of an option name.

The options are set on the top level, there is no need to use a : prefix. To set an option to false, you can use the ~ character, but please do not. Every option default value is false when not set.

The typical use of this possibility is to set the option failfast. This option alters the error processing, and it is more "bound" to the execution than to the document. It may be a better option to include it in an environment variable, or system property than in the document itself. Both approaches work.

7. Resource Files and Web Resources

When the macros import or include reference a file with a name that starts with either

  • res:, or

  • https:

then these files are treated in a special way. In any other case the files are loaded from the local disk. The following two subsections detail the mechanism of these two cases.

Java Resource Files

When the file name starts with the characters res: it is a Java resource file. It means that the file is in a JAR file among the classes. The JAR file has to be on the classpath. When Jamal is started from the command line then the JAR file has to be added to the classpath. The classpath is usually after the -cp or -classpath argument of the Java command line. If Jamal is started as a Maven plugin then the configuration in the pom.xml file should include the dependency. For example to add the pomlib library JAR to the classpath you can use the following fragment in your pom.xml:

<plugin>
    <groupId>com.javax0.jamal</groupId>
    <artifactId>jamal-maven-plugin</artifactId>
    <version>1.10.4</version>
    <executions>
        <execution>
            <id>execution</id>
            <phase>clean</phase>
            <goals>
                <goal>jamal</goal>
            </goals>
            <configuration>
                ... configuration tags ...
            </configuration>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>com.javax0.jamal</groupId>
            <artifactId>jamal-pomlib</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</plugin>
Note
The module jamal-pomlib was discontinued and is not part of the current Jamal library structure.

Web Resources

Web resources can be downloaded using the https: prefix. The only protocol supported is https. Jamal does not download any resource using the unencrypted HTTP protocol.

It is possible to cache the downloaded files. The environment variable JAMAL_HTTPS_CACHE can define a directory to store the web resources. In case the environment variable is not defined then the default value ~/.jamal/cache/ will be used. If the cache directory exists Jamal will store there the downloaded files. Jamal will create the subdirectories if needed, but Jamal will never create the cache directory itself. If you do not want to use the caching then do not create the cache directory.

Jamal will not cache a downloaded files that has SNAPSHOT in the URL (all capital letters). There is no cache eviction or expiration. You can find the files in the cache directory in subdirectories. You can also find there corresponding property files that contain information about the caching. These properties files are information purpose and Jamal does not use them at the moment.

The connection to the web can be configured if needed. The environment variables that can be used are the followings:

  • JAMAL_CONNECT_TIMEOUT, and

  • JAMAL_READ_TIMEOUT

can define two timeout values for the web download in millisecond as unit.

The default value for the timeouts is 5000, meaning five seconds.

The proxy setting can be configured using standard Java system properties. For more information see the JavaDoc documentation of the class java.net.HttpURLConnection in the JDK documentation.

8. Error Messages

When there is a processing error then Jamal throws a Java exception. The message of the exception contains at the end the location of the error. Sometimes it is not simple to identify the location due to the string replacement nature of Jamal processing. To help locating the error as precise as possible the location is given as a series as triplets. Each triplet contains a file name, a line number and a column number. The location is given in the following format:

file/line:column

When a file includes or imports another file then the location of the file is given in the error message along with the location from where the file is included or imported. The format in this case is

file/line:column <<< file/line:column

If the include/import hierarchy is deeper then the location is also given in several levels. In some cases the hierarchy is created in the error message inside a single file to help locating the error.

9. Snippet Handling

Snipetts are text fragments from the source code or from other text files that are to be included into the documentation as samples. Jamal Snippet handling macros can automate the copiing of such lines, but they can do more. These macros can trim, line number, transform the source text before inserting into the output document.

The snippet handling macros are documented in the Snipped module readme. These macros include also date handling, run-time checking of existence of referenced files, directores, classes, methods and so on.

10. Groovy Integration

The Groovy language integration lets you include Groovy code fragments into your document. These scripts are executed during the file processing, and can programmatically calculate the macro outputs.

The integration and how to use the module macros are described in the Groovy readme.

11. Ruby Integration

The Ruby language integration lets you include Ruby code fragments into your document. These scripts are executed during the file processing, and can programmatically calculate the macro outputs.

The integration and how to use the module macros are described in the Ruby readme.

12. Yaml Integration

The Yaml module can be used to simplify the maintenance of Yaml files using macros and splitting up the Yaml files into smaller chunks. The documentation of the module is jamal-yaml/README.adoc[Yaml readme]

13. IO Module

The Io module can be used to write text into separate files during the processing of a Jamal input file. The documentation of the module is jamal-io/README.adoc[Io readme]

14. Jamal API

Embedding Jamal into an application is very simple. You need the Jamal libraries on your classpath. If you use Maven, you can simply have

<dependency>
    <groupId>com.javax0.jamal</groupId>
    <artifactId>jamal-engine</artifactId>
    <version>1.10.4</version>
</dependency>

in your pom file.

The library jamal-engine transitively depends on the other libraries that are needed (jamal-core, jamal-api and jamal-tools).

You also have to specify that you use these modules (Java 9 and later) if your code uses modules.

module jamal.maven {
requires jamal.api;
requires jamal.tools;
requires jamal.engine;
}

The code invoking Jamal needs a processor that will process the input.

import javax0.jamal.engine.Processor;

var processor=new Processor(macroOpen,macroClose);
var result=processor.process(input);

The macroOpen and macroClose parameters are String values. The parameter input to the method process() has to be an object that implements the javax0.jamal.api.Input interface. The easiest way to do that is to use the readily available class javax0.jamal.tools.Input.

You can see an example to create an Input from an existing file in the jamal-maven-plugin module. The method createInput() reads a file, and then it creates a new input:

private Input createInput(Path inputFile) throws IOException {
    try (final var lines = Files.lines(inputFile)) {
        final var fileContent = lines.collect(Collectors.joining("\n"));
        return new javax0.jamal.tools.Input(fileContent, new Position(inputFile.toString(), 1));
    }
}

An Input holds the content the processor has to process. It also has a reference file name used to resolve the absolute names of the included and imported files. It also keeps track of the line number, and the column of the actual character as the macro evaluation progresses. A new Position(s,1) creates a new position that identifies the file by the name’s` and the line number 1.

When a new processor is instantiated, it uses the ServiceLoader mechanism to find all the built-in macros that are on the classpath. If your application has special macros implemented in Java then you can just put the library on the modulepath. If the classes are defined in the provides directive of the module then Jamal will find and load them automatically.

It is also possible to define user-defined and built-in macros via API. To do that you need access to the MacroRegister object that the Processor object has. To get that you can invoke the method getRegister() on the processor object:

var register=processor.getRegister();

The register has API to define macros and user-defined macros. For further information see the API JavaDoc documentation.

There is a very simple API class that makes it possible to use Jamal as a templating engine. The utility class javax0.jamal.Format has the method public static String format(String content, Map<String, String> predefinedMacros) that can format the content string using the entries of the predefinedMacros as user-defined macros. These macros eventually cannot have arguments. This is a simplified interface to access the functionality of Jamal.

15. JavaDoc

The current and past versions of the JavaDoc can be read online at the address:

16. Maintenance of this document

The source of this document is README.adoc.jam. The Jamal conversion uses the snippet macros and some aux built-in macros. The conversion is part of the execution of the tests. The code samples are automatically executed using this process, and the sample output is automatically inserted into the document.

You might also like...

Create different patterns and designs using your favorite programming language for this project.

Create different patterns and designs using your favorite programming language for this project.

Patterns project for Hacktoberfest Create different patterns and designs using your favourite programming language weather it be a square pattern, sta

Oct 5, 2022

LaetLang is an interpreted C style language. It has file reading/writting, TCP network calls and awaitable promises.

LaetLang 💻 LaetLang is an interpreted C style language built by following along Robert Nystrom's book Crafting Interpreters. This is a toy language t

Mar 14, 2022

Compiler that compiles our language flowg to g-code (4th semester project)

flowg FlowG is a language that greatly simplifies manual g-code programming. It is a high-level language that supports functions, for loops, if statem

Jun 15, 2022

Java Compiler for the MiniJava language

Java Compiler for the MiniJava language Setup Our project requires the following tools with the specified versions. Tool Version Java = 14 Maven 3 Th

Dec 5, 2022

Code4Me provides automatic intelligent code completion based on large pre-trained language models

Code4Me Code4Me provides automatic intelligent code completion based on large pre-trained language models. Code4Me predicts statement (line) completio

Dec 5, 2022

Scripting language written in, and, designed to communicate with, java

Scripting language written in, and, designed to communicate with, java

mi-lang Scripting language designed to communicate with java, to allow for easy plugins, addons, etc in any project, all without having to create an e

Dec 17, 2022

Official Java library for the DeepL language translation API.

DeepL Java Library The DeepL API is a language translation API that allows other computer programs to send texts and documents to DeepL's servers and

Dec 29, 2022

A short assembly macro-processor script to simulate the process and show the different stages

A short assembly macro-processor script to simulate the process and show the different stages

Mar 9, 2022

An All-In-One Macro for Hypixel Skyblock. Includes numerous features for Quality of Life that do NOT abide by the Hypixel Rules.

AIOMacro An All-In-One Macro for Hypixel Skyblock. Includes numerous features for Quality of Life that do NOT abide by the Hypixel Rules. Installation

Dec 19, 2022

lazy-language-loader improves loading times when changing your language by only reloading the language instead of all the game resources!

lazy-language-loader lazy-language-loader improves loading times when changing your language by only reloading the language instead of all the game re

Sep 7, 2022

This application can recognize the sign language alphabets and help people who do not understand sign language to communicate with the speech and hearing impaired.

This application can recognize the sign language alphabets and help people who do not understand sign language to communicate with the speech and hearing impaired.

Sign Language Recognition App This application can recognize the sign language alphabets and help people who do not understand sign language to commun

Oct 7, 2021

Kotlin-decompiled - (Almost) every single language construct of the Kotlin programming language compiled to JVM bytecode and then decompiled to Java again for better readability

Kotlin: Decompiled (Almost) every single language construct of the Kotlin programming language compiled to JVM bytecode and then decompiled to Java ag

Dec 14, 2022

For Jack language. Most of codes were commented with their usage, which can be useful for beginner to realize the running principle of a compiler for object-oriented programming language.

Instructions: Download the Java source codes Store these codes into a local folder and open this folder Click the right key of mouse and click ‘Open i

Jan 5, 2023

a fast, scalable, multi-language and extensible build system

Bazel {Fast, Correct} - Choose two Build and test software of any size, quickly and reliably. Speed up your builds and tests: Bazel rebuilds only what

Jan 4, 2023

Very spicy additions to the Java programming language.

Project Lombok Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java. Never write another g

Jan 1, 2023

ANTLR (ANother Tool for Language Recognition) is a powerful parser generator for reading, processing, executing, or translating structured text or binary files.

ANTLR v4 Build status ANTLR (ANother Tool for Language Recognition) is a powerful parser generator for reading, processing, executing, or translating

Dec 28, 2022

Microservice query language

restQL-core-java restQL-core allows you to run restQL queries directly from JVM applications, making easy to fetch information from multiple services

Jan 26, 2022

CogComp's Natural Language Processing libraries and Demos:

CogCompNLP This project collects a number of core libraries for Natural Language Processing (NLP) developed by Cognitive Computation Group. How to use

Dec 20, 2022

Language-Natural Persistence Layer for Java

Permazen is a better persistence layer for Java Persistence is central to most applications. But there are many challenges involved in persistence pro

Dec 12, 2022
Comments
  • for cannot include file when using evalist

    for cannot include file when using evalist

    When tha macro for is used with the option evalist it cannot include a file to get the list of values from a file. That is because the evaluation of the list is executed on an input that has null reference and the macro include does not know where to look for the file.

    You can use the evaluation, but you cannot include files with relative file name.

    opened by verhas 1
  • Bump axios from 0.21.1 to 0.21.2 in /jamal-debug-ui

    Bump axios from 0.21.1 to 0.21.2 in /jamal-debug-ui

    Bumps axios from 0.21.1 to 0.21.2.

    Release notes

    Sourced from axios's releases.

    v0.21.2

    0.21.2 (September 4, 2021)

    Fixes and Functionality:

    • Updating axios requests to be delayed by pre-emptive promise creation (#2702)
    • Adding "synchronous" and "runWhen" options to interceptors api (#2702)
    • Updating of transformResponse (#3377)
    • Adding ability to omit User-Agent header (#3703)
    • Adding multiple JSON improvements (#3688, #3763)
    • Fixing quadratic runtime and extra memory usage when setting a maxContentLength (#3738)
    • Adding parseInt to config.timeout (#3781)
    • Adding custom return type support to interceptor (#3783)
    • Adding security fix for ReDoS vulnerability (#3980)

    Internal and Tests:

    • Updating build dev dependancies (#3401)
    • Fixing builds running on Travis CI (#3538)
    • Updating follow rediect version (#3694, #3771)
    • Updating karma sauce launcher to fix failing sauce tests (#3712, #3717)
    • Updating content-type header for application/json to not contain charset field, according do RFC 8259 (#2154)
    • Fixing tests by bumping karma-sauce-launcher version (#3813)
    • Changing testing process from Travis CI to GitHub Actions (#3938)

    Documentation:

    • Updating documentation around the use of AUTH_TOKEN with multiple domain endpoints (#3539)
    • Remove duplication of item in changelog (#3523)
    • Fixing gramatical errors (#2642)
    • Fixing spelling error (#3567)
    • Moving gitpod metion (#2637)
    • Adding new axios documentation website link (#3681, #3707)
    • Updating documentation around dispatching requests (#3772)
    • Adding documentation for the type guard isAxiosError (#3767)
    • Adding explanation of cancel token (#3803)
    • Updating CI status badge (#3953)
    • Fixing errors with JSON documentation (#3936)
    • Fixing README typo under Request Config (#3825)
    • Adding axios-multi-api to the ecosystem file (#3817)
    • Adding SECURITY.md to properly disclose security vulnerabilities (#3981)

    Huge thanks to everyone who contributed to this release via code (authors listed below) or via reviews and triaging on GitHub:

    ... (truncated)

    Changelog

    Sourced from axios's changelog.

    0.21.2 (September 4, 2021)

    Fixes and Functionality:

    • Updating axios requests to be delayed by pre-emptive promise creation (#2702)
    • Adding "synchronous" and "runWhen" options to interceptors api (#2702)
    • Updating of transformResponse (#3377)
    • Adding ability to omit User-Agent header (#3703)
    • Adding multiple JSON improvements (#3688, #3763)
    • Fixing quadratic runtime and extra memory usage when setting a maxContentLength (#3738)
    • Adding parseInt to config.timeout (#3781)
    • Adding custom return type support to interceptor (#3783)
    • Adding security fix for ReDoS vulnerability (#3980)

    Internal and Tests:

    • Updating build dev dependancies (#3401)
    • Fixing builds running on Travis CI (#3538)
    • Updating follow rediect version (#3694, #3771)
    • Updating karma sauce launcher to fix failing sauce tests (#3712, #3717)
    • Updating content-type header for application/json to not contain charset field, according do RFC 8259 (#2154)
    • Fixing tests by bumping karma-sauce-launcher version (#3813)
    • Changing testing process from Travis CI to GitHub Actions (#3938)

    Documentation:

    • Updating documentation around the use of AUTH_TOKEN with multiple domain endpoints (#3539)
    • Remove duplication of item in changelog (#3523)
    • Fixing gramatical errors (#2642)
    • Fixing spelling error (#3567)
    • Moving gitpod metion (#2637)
    • Adding new axios documentation website link (#3681, #3707)
    • Updating documentation around dispatching requests (#3772)
    • Adding documentation for the type guard isAxiosError (#3767)
    • Adding explanation of cancel token (#3803)
    • Updating CI status badge (#3953)
    • Fixing errors with JSON documentation (#3936)
    • Fixing README typo under Request Config (#3825)
    • Adding axios-multi-api to the ecosystem file (#3817)
    • Adding SECURITY.md to properly disclose security vulnerabilities (#3981)

    Huge thanks to everyone who contributed to this release via code (authors listed below) or via reviews and triaging on GitHub:

    ... (truncated)

    Commits
    Maintainer changes

    This version was pushed to npm by jasonsaayman, a new releaser for axios since your current version.


    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies javascript 
    opened by dependabot[bot] 1
  • Jamal does *not* start from JShell

    Jamal does *not* start from JShell

    If you don't have jamal already, you can not start it from JShell with jshell https://git.io/jamal, because it will throw an exception:

    [INFO] processing jamal.options
    [INFO] URL=https://repo.maven.apache.org/maven2/com/javax0/jamal/jamal-engine/1.4.1/jamal-engine-1.4.1.jar
    [INFO] download https://repo.maven.apache.org/maven2/com/javax0/jamal/jamal-engine/1.4.1/jamal-engine-1.4.1.jar
    [INFO] to file ~\.m2\repository\com\javax0\jamal\jamal-engine\1.4.1\jamal-engine-1.4.1.jar
    Exception java.io.FileNotFoundException: ~\.m2\repository\com\javax0\jamal\jamal-engine\1.4.1\jamal-engine-1.4.1.jar (The system cannot find the path specified)
          at FileOutputStream.open0 (Native Method)
          at FileOutputStream.open (FileOutputStream.java:298)
          at FileOutputStream.<init> (FileOutputStream.java:237)
          at FileOutputStream.<init> (FileOutputStream.java:187)
          at JshBoot.fetch (#12:289)
          at JshBoot.url (#12:269)
          at JshBoot.remoteRepo (#12:242)
          at JshBoot.download (#12:223)
          at JshBoot.artifactId (#12:201)
          at (#24:3)
    [INFO] EXECUTING 'java -cp ~\.m2\repository\com\javax0\jamal\jamal-engine\1.4.1\jamal-engine-1.4.1.jar javax0.jamal.cmd.JamalMain pattern=.*\.jam$ exclude= from=\.jam$ to= source=. close=} open={ target=.'
    Error: Could not find or load main class javax0.jamal.cmd.JamalMain
    Caused by: java.lang.ClassNotFoundException: javax0.jamal.cmd.JamalMain
    

    User specific details omitted.

    opened by Michael1993 1
  • Trie optimization

    Trie optimization

    Trie handling is optimized. This means that the Tries are not rebuilt every time, they become public static final immutable structures. Instead of the actual values, the IndexedPlaceHolders uses fixed indices and these are used to index the actual values (String or supplier) in an array when formatting.

    The old versions are still there to support the old version of PlantUML that uses this and is part o the dependency graph as a test dependency.

    opened by verhas 0
Releases(1.12.3)
  • 1.12.3(Oct 12, 2022)

    • Various bugfixes and dependency version updates.

    • Sorting macro is available in the snippet library, developed by Michael

    • the macro define has options for all the different "define" types, like pure, verbatim etc. Originally these can be reached using special characters, which are less verbose, but cryptic. The old syntax is still usable,but not recommended.

    • file macro in snippet package now has formatting placeholders bareNaked and nakedN as well as extensions and extensionN with the possible N values being 1,2,3,4, and 5.

    • the macro counter can save its actual value using -> . This is a shortcut to a series of macros.

    • The asciidoctor preprocessor caches the result of the last run and executes Jamal only when the input changes. It also takes the included and imported files into account.

    • a bug in the core of the processing engine that caused in some rare cases over indexing exception

    • the environment variable JAMAL_DEV_PATH now can point to a file instead of containing the replacements directly

    • Macros reading and writing a file can go through a hook that the embedding application can provide. It is used by the asciidoctor implementation to list all the files read during the processing.

    • Jamal mock library is implemented, that can be used to mock some macro for user defined macro testing

    • A warning is given when a macro is defined in a scope, but it is not used

    • Macro for supports the aliases sep andsubsep`

    • In addition to the special characters in the macro define, the behaviour can also be altered using options.

    • the option RestrictedDefineParameters is now available for the define macro, to restrict parameter names to be identifiers

    Source code(tar.gz)
    Source code(zip)
  • 1.12.2(May 9, 2022)

  • 1.12.1(Apr 16, 2022)

    When the macro for was used with the option evalist the list could not include file using a relative file name because the evaluation was done by the processor on an input that had no file reference.

    Source code(tar.gz)
    Source code(zip)
  • 1.12.0(Mar 30, 2022)

    • It is possible to include a Word doc file into another word doc file using the docx:include macro.

    • You can insert a picture into a Word document using a Jamal macro. Since picture insertion is a basic function of Microsoft Word this functionality is to be used for special purposes only.

    • The macro snip can also check if a snippet has changed using the hash parameter. There is no need to invoke a separate snip:check macro.

    • There is an Asciidoctor extension, which can be used in IntelliJ to edit Jamal extended Asciidoc in a WYSIWYG way.

    • The Asciidoctor extension emits a sed command at the end of the error report, just in case and to help the lazy.

    Source code(tar.gz)
    Source code(zip)
  • 1.11.2(Mar 9, 2022)

    Bugfix release. A bug causing index out-of-range error in DOCX processing was fixed. Experimental docx: macros were also developed and are included in this release.

    Source code(tar.gz)
    Source code(zip)
  • 1.11.1(Mar 1, 2022)

    • Fully reworked command-line interface

    • Jamal macros can be used in Microsoft Word documents

    • Io module implements io:exec and io:waitFor macros to start external processes

    • extension.xml generation in Maven extension runs in a separate thread, so it does not delay the build

    • ~/.jamal/settings.(properties|xml) can be used to configure Jamal in addition to system properties and environment variables

    • Use of the external library picocli was eliminated

    • File input converts \r\n to \n on Windows.

    • Graphviz example was added to the integration tests, runs only on properly configured systems, it needs Graphviz installed eventually.

    Source code(tar.gz)
    Source code(zip)
  • 1.11.0(Feb 9, 2022)

    • Jamal provides suggestion in case a macro name is misspelled.

    • Macro parameter handling provides suggestions when the parameter name is misspelled. The suggestions are based on the Levenshtein distance.

    • Root directory finding and converting all jamal files with exclude/include list is part of the API. This API is supposed to be used during unit test execution, which creates the documentation from the Jamal files. Finding the project root directory is also part of the API.

    • Macro statelessness was NOT checked by default in prior versions due to a bug. This bug is fixed and the macro statelessness is now checked by default. The macro statelessness check was also implemented when registering global macros.

    • Macro replaceLines can have multiple replace parameters.

    • The macro snip:transform was developed.

    • Built-in macros can query the actual name of a parameter, a.k.a. which alias was used.

    • file macro formatting supports $simpleName.

    • Template handling and Trie implementation was refactored to improve performance, and it did.

    • Macro register export also experts built-in macros.

    • New core macro named macro was added.

    • New API class JamalOutputStream was added, which is a filtering output stream.

    • Macro include has a parameter lines, which can limit which lines to include.

    • Error reporting was fixed avoiding circular exceptio references when closers were running. For the user this means cleaner error report.

    • New macros range, and untab in the snippet library. It is also supported by the snip:transform macro.

    • Macro snip:collect can collect snippets which start and stop with the asciidoc tag notation: tag::name[] and end::name[].

    • Macro snip implements the poly option to concatenate snippets.

    • dependencies following the latest releases

    • import and include macros implement a new option noCache.

    • Maven extension can keep its own extensions.xml automatically up-to-date.

    • https include and import cache can be configured to evict entries.

    • macro rot13

    • improved error reporting

    Source code(tar.gz)
    Source code(zip)
  • 1.10.4(Jan 5, 2022)

  • 1.10.3(Dec 14, 2021)

  • 1.10.2(Dec 10, 2021)

    The position in error messages became hierarchical showing the position not only where the error is, but also where the actual file was imported, included from. Snippets can be collected from resource and from teh web using file names that start with res: and https://. Snippet collection still fails when trying to collect snippets from binary files, but the error message is more readable. SnipCheck can be switched off using -Djamal.snippet.check=false SnipLoad and SnipSave macros were developed letting the macro save and/or load snippets from an XML file string:xxx macros now properly handle their arguments and do not use the whole input as an argument. It makes difference in case of leading spaces. Xml formatting is fixed. Former formatting deleted the new lines from the output, that adversely affected CDATA content. The new format fixes this and also adds a trailing \n at the end of the XML file.

    Source code(tar.gz)
    Source code(zip)
  • 1.10.1(Nov 26, 2021)

    The snippet library was extended with two new macros xml:define and xml:insert. When an XML user-defined macro is used without an argument then the whole XML formatted is returned.

    Source code(tar.gz)
    Source code(zip)
  • 1.10.0(Nov 23, 2021)

    New macro defer, which evaluates its input after the whole input was processed in a closer.

    Due to a bug the backslash character did not escape the following newline after an escape macro (ironic). Fixed.

    The old-style macro evaluation is not available anymore. This significantly sped up the processing. There were bug fixes for bugs that in some situations prevented the proper handling of ~/... format file names.

    Some environment variables did not have the system property pair. Fixed.

    The maven plugin when used to convert a project to a Jamalized project does not create .mvn/extensions.xml in the subdirectories anymore.

    There is a new environment variable JAMAL_DEV_PATH and system property jamal.dev.path. See the documentation.

    A bug prevented file include in Windows in some special cases. Fixed.

    snipline NAME can be used to define a single line snippet without an end snippet.

    Options noUndefault and emptyUndef are handled by macro evaluation.

    xmlFormat works even in applications that embed Jamal in multi-thread.

    snip:check is reworked, extended and improved.

    The core macro if now has several options and it is possible to test numeric comparisons as well as string emptiness.

    The handling of lenient option has changed. From now on lenient has to be a global option.

    Environment variable handling was refactored and the documentation was moved to the class defined in the API module.

    JavaScript dependencies were upped to newer versions to avoid security issues.

    Counter macros (from snippet) can be invoked with the parameter last to simply return the last value.

    KillLine macro has to option keep that reverses which lines to keep and which lines to keep.

    There is a new snip:lineCount macro that returns the number of lines in a snippet.

    Options are not stored in option stores anymore. Options are simple Identified objects stored along with the user-defined macros. This also means that options can individually be exported, and the whole options store cannot be exported anymore in one.

    Source code(tar.gz)
    Source code(zip)
  • v.1.9.1(Oct 29, 2021)

  • 1.9.0(Oct 22, 2021)

    Maven extension module is developed. Using this module there is no need to preprocess pom.xml.jam or pom.jam files. Maven automatically reads those files instead of the pom.xml using the extension. Ruby scripts do not share the global variables anymore. It was a bug that the differently named Ruby scripts used the same set of global variables. Ruby and Groovy macros can be configured using options and not only user defined macros. PlantUml macro also uses options and not only user defined macros to define the parameters like the image directory. Built-in macros can have multiple names, and the assertion package immediately starts to use it so equals and equal ending denote the same macro. Evaluate can evaluate macros in its input in a loop till all macros get evaluated. Environment variable can be queried to throw exception when the variable is not defined.

    Source code(tar.gz)
    Source code(zip)
  • 1.8.0(Oct 14, 2021)

    SnipCheck was introduced to enforce snippet and documentation consistency Assertions package was developed Macro statefulness is checked XML snippet reading bug (using CWD instead of document dir) was fixed dependencies updated to the latest releases

    Source code(tar.gz)
    Source code(zip)
  • 1.7.9(Jul 11, 2021)

  • 1.7.8(Jul 1, 2021)

    This release opens the debugger package, and so it can be used from Java::Geci. It still needs investigation why this is needed, though, but this patch solves this issue.

    io:delete gives more meaningful error messages

    TestWrite did not check that the output was really written. Fixed.

    Various documentation and JavaDoc fixes. SNAKE Yaml now uses the latest version and not an outdated one. Build runs with Github action Various tests and some production code were fixed so that the build runs also on Windows and Linux.

    Source code(tar.gz)
    Source code(zip)
  • 1.7.7(May 26, 2021)

    Markdown module was added with one single macro. Using this you can use markdown in JavaDoc files. Macro can implement its own fetching, and that way now escape macro can also be aliased. Option nl is removed, does not exist any more. Any \ after a macro escapes the next new line character. For has new keyword from to iterate through a collection that a user defined ObjectHolder macro can provide.

    Source code(tar.gz)
    Source code(zip)
  • 1.7.6(May 12, 2021)

    Yaml XML macro extended to have attributes and CDATA in the output when you design a Yaml, especially for XML. Macro define can specify optional parameters. It is an error to use := on a parameterless macro without () to avoid ambiguity. Even I, who created the whole thing, could not remember if a:= defines a global or a pure macro.

    Source code(tar.gz)
    Source code(zip)
  • 1.7.5(May 4, 2021)

    yaml can be exported as XML debugger can handle breakpoints, UI was changed yaml macros Add and isResolved are added macro tests can now be written an jyt (Jamal Yaml Test) files

    Source code(tar.gz)
    Source code(zip)
  • 1.7.4(Apr 25, 2021)

    JavaDoc support Yaml support jamal-io module writing file and stdout and stderr various bugfixes collect can collect onceAs verbatim user-defined macros default macro can get the actual macro name snippet trim macro can verticalTrimOnly macro use can define alias for already existing macro

    Source code(tar.gz)
    Source code(zip)
  • 1.7.3(Apr 12, 2021)

    An interactive debugger was developed for Jamal transformation to follow the transformation step-by-step. Jamal can be started using jbang. command-line parameters are refactored and much more user-friendly.

    Source code(tar.gz)
    Source code(zip)
  • 1.7.2(Feb 28, 2021)

    New module integrating the Ruby scripting language A bug is fixed that caused reporting the wrong error when there was an error inside an included file.

    Source code(tar.gz)
    Source code(zip)
  • 1.7.1(Feb 26, 2021)

    This release includes a Groovy module that you can use to embed Groovy code into the Jamal input.

    Closer objects are invoked in the order they were (first) declared. bug fixed and makes it possible to use :a user-defined macros when USED and not only when defined Test support can set the separators after the input is specified. Cast tool was created in the tool module and use was moved from snippet to there.

    Source code(tar.gz)
    Source code(zip)
  • 1.7.0(Feb 23, 2021)

    New macro to undefine a user-defined macro. Built-in macros can do post-processing where they can modify the final result. jamal-snippet macro xmlFormat uses the new functionality and can format the whole document at the end Embedding application can use a general 'context' that can also be used by the macros

    Source code(tar.gz)
    Source code(zip)
  • 1.6.5(Feb 13, 2021)

  • 1.6.4(Feb 7, 2021)

    Snippet collection throws an error when a snippet is not closed but only in case the snippet is used. Unclosed macro opening character reported line number is correct after a bug fixed that reported the last opened macro line number. documentation about how to write a built-in macro was started phantom parameters are handled correctly in case a macro does not have a parameter but there are zero string resulting macros evaluated in the parameters InputHandler got a new startWith method ScriptBasic module was reintegrated, following the release and is now part of the release it is possible to define a user defined macro default which is used in case a macro is not defined. The real good use of it is when the user defined macro is defined using Java support and has special logic.

    Source code(tar.gz)
    Source code(zip)
  • 1.6.3(Jan 30, 2021)

  • 1.6.2(Jan 28, 2021)

  • 1.6.1(Jan 28, 2021)

Owner
Peter Verhas
Senior Software at EPAM Systems
Peter Verhas
lazy-language-loader improves loading times when changing your language by only reloading the language instead of all the game resources!

lazy-language-loader lazy-language-loader improves loading times when changing your language by only reloading the language instead of all the game re

Shalom Ademuwagun 7 Sep 7, 2022
For Jack language. Most of codes were commented with their usage, which can be useful for beginner to realize the running principle of a compiler for object-oriented programming language.

Instructions: Download the Java source codes Store these codes into a local folder and open this folder Click the right key of mouse and click ‘Open i

gooooooood 1.1k Jan 5, 2023
Representational State Transfer + Structured Query Language(RSQL): Demo application using RSQL parser to filter records based on provided condition(s)

Representational State Transfer + Structured Query Language: RSQL Demo application using RSQL parser to filter records based on provided condition(s)

Hardik Singh Behl 9 Nov 23, 2022
MFP (Mathematic language For Parallel computing) Android library

MFPAndroLib This is MFP (Mathematic language For Parallel computing) Android library project. MFP is a novel scripting programming language designed a

Tony Cui 6 Sep 5, 2022
Metremenqeemi - Android/iOS app to teach the Coptic Language

ⲙⲉⲧⲣⲉⲙⲛ̀ⲭⲏⲙⲓ The Open Source Android/iOS app to learn how to read and understand the Coptic Language Join our Discord Channel About the Curriculum The

Mark Yacoub 8 Aug 30, 2022
Unofficial community-built app for the Japanese language learning tool jpdb.io.

jpdb-android Unofficial community-built app for the Japanese language learning tool jpdb.io. While the web app works in most scenarios, the goal with

null 3 Feb 15, 2022
Very spicy additions to the Java programming language.

Project Lombok Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java. Never write another g

Project Lombok 11.7k Dec 30, 2022
JurjenLang, an interpreted programming language

JurjenLang An untyped interpreted functional programming language Getting started Follow these three steps on your computer to get started git clone h

JVerbruggen 5 May 3, 2022
A list of direct references to classes and interfaces in the Java Language Specification (3d Ed.)

A list of direct references to classes and interfaces in the Java Language Specification (3d Ed.) and a program to compute the indirectly required classes and interfaces

Joshua Bloch 12 Jun 3, 2022
A dubbo gateway based Java language.

A dubbo gateway based Java language.

老夫正年轻 19 Sep 24, 2022