Skip to content

Add hexadecimal integer literals (e.g. 0xFF) - #132

Merged
ddekany merged 1 commit into
apache:2.3-gaefrom
gdisirio:feature/hex-integer-literals
Aug 8, 2026
Merged

Add hexadecimal integer literals (e.g. 0xFF)#132
ddekany merged 1 commit into
apache:2.3-gaefrom
gdisirio:feature/hex-integer-literals

Conversation

@gdisirio

Copy link
Copy Markdown
Contributor

Whole numbers can now be written in hexadecimal notation, using an 0x (or 0X) prefix followed by hex digits — 0xFF is 255, 0x100 is 256.

This has come up on the mailing list several times over the years; the usual workaround has been exposing Integer.decode as a shared variable. It's most useful when templates deal with values that are conventionally written in hex — bitmasks, hardware register values, colour constants, binary protocol fields — where decimal spellings obscure the intent.

Backward compatibility

Purely additive. In an expression, a sequence like 0xFF was previously a parse error (a number immediately followed by an identifier), so no currently-valid template can change meaning. The new HEX_INTEGER token is only active in the expression lexer states, never in plain template text.

Range and representation

Hex literals specify whole numbers only — no fractional part, no exponent, no sign. A sign can still be applied as a separate operator, as in -0x10.

Values are narrowed to the smallest of Integer / Long that fits, and beyond Long range they become BigInteger, so there is no arbitrary limit on the number of digits and no overflow error.

One consequence worth flagging explicitly: this means hex literals do not go through ArithmeticEngine.toNumber, so under the default BIGDECIMAL_ENGINE the literal 255 yields a BigDecimal while 0xFF yields an Integer. The two compare and arithmetically behave identically, but they are different Java types, which is observable if the value is passed to an overloaded Java method. I went this way because hex notation is inherently integral and Integer/Long is what callers doing mask arithmetic expect — but if you'd rather hex respect the configured arithmetic engine for consistency, that's a small change and I'm happy to make it.

Testing & docs

  • JUnit coverage in HexLiteralTest: basic values, both prefix cases, use in expressions, assignment, comparison, and the Integer/Long/BigInteger boundaries.
  • Manual entry in the "Direct specification of values" section, marked @since 2.3.35.
  • ./gradlew check and ./gradlew manualOffline both pass.

Relationship to #130

Independent of #130 — the two share no code. This changes only the lexer/parser (FTL.jj); #130 changes only the string built-ins (BuiltIn.java, BuiltInsForStringsBasic.java). The one file both touch is book.xml, and the hunks are about 10,000 lines apart (the expressions chapter vs. the built-ins reference). They merge cleanly in either order, so no coordination is needed.

Whole numbers can now be written in hexadecimal notation, using the
0x (or 0X) prefix followed by hex digits, e.g. 0xFF for 255. This has
been requested on the mailing list several times over the years (the
previous workaround being to expose Integer.decode as a shared
variable).

The change is purely additive and backward compatible: in an
expression, a sequence like "0xFF" was previously a parse error (a
number immediately followed by an identifier), so no currently-valid
template can be affected. The new HEX_INTEGER token is only active in
the expression lexer states, never in plain template text.

Hex literals specify whole numbers only; the sign can be applied as a
separate operator (e.g. -0x10). There's no limit on the number of
digits: the value is narrowed to an Integer if it fits into a 32-bit
signed integer, to a Long if it fits into a 64-bit signed integer, and
is kept as a BigInteger otherwise.

Added JUnit coverage and a note in the Manual's "Direct specification
of values" section (with @SInCE 2.3.35).
@ddekany
ddekany merged commit 667686b into apache:2.3-gae Aug 8, 2026
3 checks passed
@ddekany

ddekany commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Thanks! Also added followup commit in 2.3-gae (like I did that movement into ArithmeticEngine)

gdisirio added a commit to gdisirio/freemarker-codegen that referenced this pull request Sep 5, 2026
Moves the fork onto the official 2.3.35 release, which is the first one to
contain the string built-ins and hexadecimal literals that were contributed
upstream from this fork (PRs apache#130 and apache#132). The fork's own versions of both
are dropped in favour of the released ones, which differ:

?indent(prefix) now adds the prefix to every line and right-trims each
resulting line, with an optional 2nd boolean argument to switch that off.
?dedent is called without parentheses in its no-argument form, and
?dedent(prefix) removes the longest matching prefix of the prefix from every
line, also trimming. Whitespace-only lines count as empty throughout. These
were arrived at during review; see the PR discussions.

?right_pad_lines is gone. Aligning a column of lines is better done by
splitting and looping, which keeps every single-line built-in available and,
unlike a whole-block built-in, lets you append after the padding of each line
(as C macro continuations require). The upstream ?lines built-in, new in
2.3.35, is what to split with. The README section is rewritten accordingly.

Hexadecimal parsing moved into the new overridable
ArithmeticEngine.hexadecimalToNumber, so FTL.jj delegates to it instead of
parsing hex itself. The default implementation keeps the Integer/Long/
BigInteger narrowing, so oversized literals like 0xFFFFFFFFFFFFFFFF still
parse exactly, which matters for masks in code-first mode.

CommandLine.java is kept although 2.3.35 removed it: this fork wires it as the
jar's Main-Class so that "java -jar" reports the codegen version, and a real
CLI is planned. That is a deliberate divergence from upstream, which removed
all main() methods because security scans flag them.

version.properties now reports the upstream version as the released 2.3.35
rather than 2.3.35-nightly; codegenVersion stays 0.1.0.
@gdisirio
gdisirio deleted the feature/hex-integer-literals branch September 5, 2026 07:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants