public class SnippetSet
extends java.lang.Object
Snippets are a templating system tailored for code generation. In contrast to other
templating approaches, snippets produce structured documents based on the principles of a
Wadler-Lindig pretty printer (see Doc). They are thus better suited for producing source
code with spacing and indentation requirements. The decoupling of layout of the snippet code
from the generated source also allows for producing better readable snippet definitions.
Furthermore, snippets support smooth interop with Java, and a rich set of control structures.
A snippet is defined by a declaration as follows:
@snippet add(x,y)
{@x} + {@y}
@end
Evaluating add(1,2) will produce 1 + 2. More precisely, it will produce
Doc.text("1").add(Doc.text(" + ")).add(Doc.text("2")).
Unless specified otherwise, all elements in a snippet are joined within a vertical group with
nesting of 0 and the separator Doc.BREAK for a line break. This can be overridden in the
snippet header, for example:
@snippet add(x,y) auto 4
{@x}
+ {@y}
@end
The layout specification following the parameter list of a snippet has the following general syntax:
[ fill | vertical | horizontal | auto ] [ NUMBER ] [ on EXPR ]
Fill, vertical, horizontal, and auto describe the grouping mode (see
Doc.group(Doc.GroupKind, Doc) for definitions), NUMBER the indentation inserted after
the first break, and EXPR the separator to use (expressions are described below). The default is
vertical 0 BREAK. A snippet output group is wrapped with Doc.align(). See the
documentation of Doc for details, or the papers cited there.
Indentation of subsequent lines in a snippet definition will be trimmed based on the indentation of the first line. Therefore, nesting for control structures does not introduce indentation. However, in the subsequent definition the inner block will be indented as expected:
@snippet block(stm)
{
{@stm}
}
@end
Lines in the snippet source can be joined to avoid overflow. Such joined lines appear as one logical line to the snippet engine. For example:
@snippet add(x,y) auto 4
{@x} \
+ {@y}
@end
Leading space from a line continuation is trimmed, however, any space before the \ is
not. In order to escape a backslash, use @\.
Snippet code can be documented with # comments. These can appear both inside and
outside snippet definitions:
# Renders an advanced math equation.
@snippet add(x,y)
# This is the equation.
{@x} + {@y}
@end
Inline comments (comments on the same line as executed code) are not supported.
A snippet expression is a text enclosed as {@expr}. Expressions evaluate to objects and are finally converted to strings before inserted into the snippet output. The following expression forms are supported:
literal: an integer or a "-quoted string. A string denotes a value of type
Doc, and Doc methods can be called on it.
@@: evaluates to @.
var: a variable reference. Variables are introduced by parameters, iterators, and
globals which are passed into a snippet set at construction time. The predefined variables
BREAK, SOFT_BREAK, EMPTY correspond to the according constants in
Doc. TRUE and FALSE denote boolean values.
name(expr1,...,exprN): a call to another snippet.
expr.name: selects the value of a public field in the Java object denoted by
expr, or calls a zero-parameter method of this name.
expr.name(expr1,...,exprN): calls a public method on the Java object denoted by
expr.
expr1 R expr2, where R is any of ==, !=, <,
<=, >, >=. Calls the comparison relation on the operands, resulting
in either the constant TRUE or FALSE. For comparison, a Doc or enum value will be first
converted to a string. After that, operands are either compared with their equals method or, for
metric relations, must have the same type and implement the Comparable interface.
Values passed and returned to Java methods are converted on the fly to strings, numbers, and
enum values as demanded by the context type. When a method is applied on an iterable and the
name cannot be resolved, an attempt is made to wrap the iterable in a FluentIterable,
making methods like append, first, etc. available.
A few control structures are supported by a snippet. A conditional is written as follows,
where the else-part is optional, and the then-part is taken if the condition expression
evaluates to a value depending on its type: true for boolean, a non-zero value for
numbers, a non-empty value for strings, a document which contains more than whitespace, or an
iterable which contains at least one element:
@if expr
...
@else
...
@end
A join is written as follows: expr1 must evaluate to an iterable. The optional
conditional, if specified, forces the iterable to only evaluate on elements where expr2
returns true or the equivalent (see above). The optional layout specifies how the
elements in the body are joined (layout has the same syntax as with snippet definitions):
@join var : expr1 [ if expr2 ] [ layout ]
...
@end
A let is written as follows, where var is bound over the scope of the let:
@let var1 = expr1, var2 = expr2, ...
...
@end
Finally, a switch is written as such, where the default-part is optional:
@switch expr
@case expr
...
@case expr
...
@default
...
@end
Snippet sources can extend other snippet sources using the following syntax:
@extends "some/file.snip"
All extends-clauses must be at the beginning of a snippet source.
A snippet can be defined private to a file, which is good practice to specify the interface between different files in a snippet set:
@private add(x)
...
@end
Definitions coming from extended sources can be overridden if the override-marker is used:
@override add(x)
...
@end
It is not possible to override definitions in the same source. Also, a snippet can only be overridden if it is in the direct extension path. For example, if sources A and B are extended by C, and B attempts to override a definition in A (without extending it explicitly), an error will be produced.
A snippet can be declared abstract:
@abstract add(x)
An abstract snippet has no body. Attempting to evaluate an abstract snippet results in a runtime error.
The bind(Class, Map) method allows to bind an interface to a snippet set,
implementing this interface via the methods in the set. This allows for a typed access to the
snippet definitions from Java. The snippet engine attempts to convert to and from types used in
the interface, using standard string-based conversion methods if needed.
A runtime error is raised if a method in an interface cannot be bound to any snippet. It is allowed to bind an abstract snippet, however. An attempt to evaluate it will result in a runtime error. Hence, abstract snippet are a way to express that certain functionality stays unimplemented in a snippet set.
It is good practice to declare a snippet which is not intended to be bound to an interface and not used by other snippet files as private:
@private add(x)
...
@end
This snippet will be only available for calls from the same snippet file. Its name is unique to the file and cannot clash with similar named snippets from other files.
| Modifier and Type | Class and Description |
|---|---|
static class |
SnippetSet.EvalException
Represents an evaluation error, with an issue describing it.
|
static interface |
SnippetSet.InputSupplier
An interface supplying source input for the @extends command.
|
static class |
SnippetSet.Issue
Represents an issue (error) with either snippet parsing or runtime evaluation.
|
static class |
SnippetSet.ParseException
Represents a parsing error, with a collection of issues.
|
| Modifier and Type | Method and Description |
|---|---|
Context |
baseContext()
Returns a context for snippet evaluation which binds all snippets in this set.
|
Context |
baseContext(java.util.Map<java.lang.String,java.lang.Object> globals)
Returns a context for snippet evaluation which binds all snippets in this set and
the given globals.
|
<T> T |
bind(java.lang.Class<T> interfaceType)
Binds the snippet set to the given interface.
|
<T> T |
bind(java.lang.Class<T> interfaceType,
java.util.Map<java.lang.String,java.lang.Object> context)
Like
bind(Class), but allows to provide a map which defines
global variables accessible by the snippets. |
static <T> T |
createSnippetInterface(java.lang.Class<T> interfaceType,
java.lang.String snippetResourceRoot,
java.lang.String snippetResource)
Returns the given interface type bound to an instance of
SnippetSet parsed from the
given snippet resource file. |
static <T> T |
createSnippetInterface(java.lang.Class<T> interfaceType,
java.lang.String snippetResourceRoot,
java.lang.String snippetResource,
java.util.Map<java.lang.String,java.lang.Object> globals)
Returns the given interface type bound to an instance of
SnippetSet parsed from the
given snippet resource file. |
<T> T |
eval(java.lang.Class<T> requestedType,
java.lang.String name,
Context context,
java.util.List<java.lang.Object> args)
Evaluates the named snippet with given context and requested result type.
|
Doc |
eval(java.lang.String name,
java.lang.Object... args)
Shortcut for evaluating a snippet with the
baseContext() into a Doc. |
static SnippetSet.InputSupplier |
fileInputSupplier(java.io.File root)
Returns an input supplier which works on the given root file.
|
static SnippetSet |
parse(java.io.File file)
Parses the input from a file and returns a snippet set.
|
static SnippetSet |
parse(SnippetSet.InputSupplier supplier,
java.lang.String inputName)
Parses the input and returns a snippet set.
|
static SnippetSet.InputSupplier |
resourceInputSupplier(java.lang.String root)
Returns an input supplier which works on the given resource root path.
|
public static SnippetSet parse(SnippetSet.InputSupplier supplier, java.lang.String inputName) throws SnippetSet.ParseException
SnippetSet.ParseExceptionpublic static SnippetSet.InputSupplier fileInputSupplier(java.io.File root)
public static SnippetSet.InputSupplier resourceInputSupplier(java.lang.String root)
public static SnippetSet parse(java.io.File file) throws SnippetSet.ParseException
SnippetSet.ParseExceptionpublic static <T> T createSnippetInterface(java.lang.Class<T> interfaceType,
java.lang.String snippetResourceRoot,
java.lang.String snippetResource)
SnippetSet parsed from the
given snippet resource file.public static <T> T createSnippetInterface(java.lang.Class<T> interfaceType,
java.lang.String snippetResourceRoot,
java.lang.String snippetResource,
@Nullable
java.util.Map<java.lang.String,java.lang.Object> globals)
SnippetSet parsed from the
given snippet resource file. The passed map is used when binding the snippet set,
providing the globals for snippet execution.public <T> T bind(java.lang.Class<T> interfaceType)
SnippetSet.EvalException.java.lang.IllegalArgumentException - if not all methods are bound.public <T> T bind(java.lang.Class<T> interfaceType,
java.util.Map<java.lang.String,java.lang.Object> context)
bind(Class), but allows to provide a map which defines
global variables accessible by the snippets.java.lang.IllegalArgumentException - if not all methods are bound.public Context baseContext(java.util.Map<java.lang.String,java.lang.Object> globals)
public Context baseContext()
public <T> T eval(java.lang.Class<T> requestedType,
java.lang.String name,
Context context,
java.util.List<java.lang.Object> args)
SnippetSet.EvalExceptionpublic Doc eval(java.lang.String name, java.lang.Object... args)
baseContext() into a Doc.SnippetSet.EvalException