-
Notifications
You must be signed in to change notification settings - Fork 2
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, which requires that the if
condition be flipped:
Label else_ = mm.label();
a.ifGe(b, else_); // flipped from < to >=
<code>
else_.here();
if (a < b) {
<code>
} else {
<more 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, () -> {
<code>
}, () -> {
<more code>
});
When using explicit labels, the flipped if
condition pattern can be used here too:
Label else_ = mm.label();
a.ifGe(b, else_); // flipped from < to >=
<code>
Label cont = mm.label().goto_();
else_.here();
<more code>
cont.here();
The original if
condition can also remain the same, but then the code sections must be swapped:
Label then = mm.label();
a.ifLt(b, then);
<more code>
Label cont = mm.label().goto_();
then.here();
<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>
}
Note that the if
condition doesn't need to be flipped to break out of the loop:
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 is similar to the generic loop, but with a flipped condition and one code section:
MethodMethod mm = ...
Variable a, b = ...
Label start = mm.label().here();
Label end = mm.label();
a.ifGe(b, end); // flipped from < to >=
<code>
mm.goto_(start);
end.here();
do {
<code>
} while (a < b);
This is the simplest loop to translate. There's only one label, and the condition isn't flipped:
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>
}
This is first logically translated to this:
int i = 0;
while (i < 10) {
<code>
if (a >= b) {
<more code>
}
i++;
}
And then it can be translated using the usual if and loop patterns:
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.ifGe(10, end); // flipped from < to >=
<code>
a.ifLt(b, cont); // flipped back to original condition
<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 here
lock.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.