-
Notifications
You must be signed in to change notification settings - Fork 4
Coding patterns
This page shows how some common coding patterns can be implemented.
A class which consists solely of static methods and fields doesn't need a constructor, but a class which can be instantiated needs one. Constructors aren't added automatically, but this is the easiest way to do it:
cm.addConstructor().public_();A constructor must call the super or this constructor exactly once in the code body, or else class generation will fail. If the constructor has defined no code, and it has no arguments, a call to super() is inserted automatically. This is why the above example works, assuming that the super class also has a no-arg constructor.
This example shows explicit calls to the super or this constructor:
MethodMaker ctor1 = cm.addConstructor(String.class).private_();
ctor1.field("message").set(ctor1.param(0));
// Invoke the super class constructor. Note that it doesn't need to be the very first thing.
ctor1.invokeSuperConstructor();
MethodMaker ctor2 = cm.addConstructor().public_();
// Invoke the constructor defined above.
ctor2.invokeThisConstructor("hello");if (a < b) {
<code>
}The easiest way to generate this is to use a convenience method which relies on a lambda function:
MethodMethod mm = ...
Variable a, b = ...
a.ifLt(b, () -> {
<code>
});The more generic technique uses explicit labels:
Label then = mm.label();
a.ifLt(b, then);
Label cont = mm.label().goto_();
then.here();
<code>
cont.here();A shorter form is possible by flipping the if condition, but doing so can break floating point NaN comparisons. Use this form instead:
Label cont = mm.label();
a.lt(b).ifFalse(cont);
<code>
cont.here();if (a < b) {
<then code>
} else {
<else code>
}Like the regular if statement, a convenience method can be used which relies on lambda functions:
MethodMethod mm = ...
Variable a, b = ...
a.ifLt(b, () -> {
<then code>
}, () -> {
<else code>
});When using explicit labels, a longer form can be used:
Label then = mm.label();
a.ifLt(b, then);
<else code>
Label cont = mm.label().goto_();
then.here();
<then code>
cont.here();To avoid having to reorder the then and else sections, do this:
Label else_ = mm.label();
a.lt(b).ifFalse(else_);
<then code>
Label cont = mm.label().goto_();
else_.here();
<else code>
cont.here();The && and || operators are implemented by converting the code to a goto-based form. For example:
if (a > 10 && a < 20) {
<code>
}
<more code>Transform this into the following pseudo code first:
if (a <= 10) goto nomatch;
if (a >= 20) goto nomatch;
<code>
nomatch:
<more code>The code is generated like so:
Label nomatch = mm.label();
aVar.ifLe(10, nomatch);
aVar.ifGe(20, nomatch);
<code>
nomatch.here();
<more code>Implementing the || operator can be performed by using De Morgan's laws to convert the expression into using the && operator, or it can be done with a different goto pattern. For example:
if (a == 10 || a == 20) {
<code>
}
<more code>Here's the pseudo code:
if (a == 10) goto match;
if (a == 20) goto match;
goto cont;
match:
<code>
cont:
<more code>The code is generated like so:
Label match = mm.label();
aVar.ifEq(10, match);
aVar.ifEq(20, match);
Label cont = mm.label().goto_();
match.here();
<code>
cont.here();
<more code>Generic loops always follow the same basic pattern.
while (true) {
<code>
if (a < b) break;
<more code>
}And here's how to generate it:
MethodMethod mm = ...
Variable a, b = ...
Label start = mm.label().here();
Label end = mm.label();
<code>
a.ifLt(b, end);
<more code>
mm.goto_(start);
end.here();while (a < b) {
<code>
}This can be implemented like the generic loop, but with a flipped condition and one code section. However, as mentioned earlier, flipping the condition can break NaN comparisons. This form is safer:
MethodMethod mm = ...
Variable a, b = ...
Label start = mm.label().here();
Label end = mm.label();
a.lt().ifFalse(end);
<code>
mm.goto_(start);
end.here();
do {
<code>
} while (a < b);This is the simplest loop to translate:
MethodMethod mm = ...
Variable a, b = ...
Label start = mm.label().here();
<code>
a.ifLt(b, start);for (int i = 0; i < 10; i++) {
<code>
if (a < b) continue;
<more code>
}Here's how it can be implemented:
MethodMethod mm = ...
Variable a, b = ...
Variable i = mm.var(int.class).set(0);
Label start = mm.label().here();
Label end = mm.label();
Label cont = mm.label();
i.lt(10).ifFalse(end);
<code>
a.ifLt(b, cont);
<more code>
cont.here();
i.inc(1);
mm.goto_(start);
end.here();try {
<code>
} catch (Exception e) {
<handler code>
}When implementing a catch block, it's important that the handled code doesn't flow into the handler.
MethodMaker mm = ...
Label tryStart = mm.label().here();
<code>
Label cont = mm.label().goto_(); // branch past the handler
Label tryEnd = mm.label().here();
var e = mm.catch_(tryStart, tryEnd, Exception.class);
<handler code>
cont.here();An easier technique can be used which uses a callback:
MethodMaker mm = ...
Label tryStart = mm.label().here();
<code>
mm.catch_(tryStart, Exception.class, e -> {
<handler code>
});
// non-exception case continues herelock.lock();
try {
<code>
if (a < b) return;
<more code>
} finally {
lock.unlock();
}With finally statements, every path which can exit the try block must be handled. The
built-in finally_ method makes this pattern automatically.
MethodMaker mm = ...
Variable lock, a, b = ...
lock.invoke("lock");
Label tryStart = mm.label().here();
<code>
a.ifLt(b, () -> mm.return_());
<more code>
mm.finally_(tryStart, () -> lock.invoke("unlock"));Consider referencing variables as final. This doesn't prevent their modification, but it does prevent mistakes like this:
MethodMaker mm = ...
var a = mm.param(0);
var b = mm.param(1);
Label skip = mm.label();
b.ifLt(0, skip);
a = a.add(b); // oops
skip.here();
mm.return_(a);The above code produces this exception when the class is finished:
java.lang.IllegalStateException: Accessing an unassigned variable: type=int, name=null (method: "test")
This is caused by the creation of two a variables, which aren't guaranteed to be assigned for all execution paths. Older versions of the library didn't detect the problem, and instead a VerifyError is thrown when the class is loaded.
By declaring the variables as final, the original code won't compile, and the correct version is:
MethodMaker mm = ...
final var a = mm.param(0);
final var b = mm.param(1);
Label skip = mm.label();
b.ifLt(0, skip);
a.set(a.add(b)); // correct
skip.here();
mm.return_(a);The correct version assigns a new value to the existing a variable instead of replacing it.
Although a temporary variable is still created by the add operation, it doesn't appear in
the generated code.