RPNXCortado
C++23 JVM class-file, bytecode, validation, and JAR toolkit
Loading...
Searching...
No Matches
tutorial_codegen.cpp
1// AI Generated 2026 Ryan P. Nicholl <rnicholl@protonmail.com>
2// SPDX-License-Identifier: Apache-2.0
3
4#include "tutorial_support.hpp"
5
6#include <cstddef>
7#include <cstdint>
8#include <filesystem>
9#include <fstream>
10#include <iostream>
12#include <rpnx/cortado/jar.hpp>
14#include <span>
15#include <stdexcept>
16#include <string>
17#include <vector>
18
19int main(int argument_count, char const* const* argument_values)
20try
21{
22 std::filesystem::path const output_directory = argument_count == 2 ? std::filesystem::path(argument_values[1]) : std::filesystem::current_path();
23 std::filesystem::create_directories(output_directory);
24
25 // [create-class]
26 rpnx::cortado::class_file_builder builder("tutorial/Generated", "java/lang/Object", {0, 61});
27 static_cast< void >(builder.add_field(
28 "invocations",
29 "I",
30 rpnx::cortado::field_access_flags::is_public |
31 rpnx::cortado::field_access_flags::is_static));
32
34 constructor.aload({0})
35 .invokespecial("java/lang/Object", "<init>", "()V")
36 .append< rpnx::cortado::opcode::return_ >();
37 static_cast< void >(builder.add_method(
38 "<init>",
39 "()V",
40 rpnx::cortado::method_access_flags::is_public,
41 constructor));
42 // [create-class]
43
44 // [sum-to]
46 rpnx::cortado::label const sum_entry = sum_to.new_label();
47 rpnx::cortado::label const sum_loop = sum_to.new_label();
48 rpnx::cortado::label const sum_done = sum_to.new_label();
49 rpnx::cortado::label const sum_end = sum_to.new_label();
50
51 sum_to.bind(sum_entry)
52 .append< rpnx::cortado::opcode::iconst_0 >()
53 .istore({1})
54 .append< rpnx::cortado::opcode::iconst_1 >()
55 .istore({2})
56 .bind(sum_loop)
57 .iload({2})
58 .iload({0})
59 .branch< rpnx::cortado::opcode::if_icmpgt >(sum_done)
60 .iload({1})
61 .iload({2})
62 .append< rpnx::cortado::opcode::iadd >()
63 .istore({1})
64 .iinc({2}, 1)
65 .branch< rpnx::cortado::opcode::goto_ >(sum_loop)
66 .bind(sum_done)
67 .iload({1})
68 .append< rpnx::cortado::opcode::ireturn >()
69 .bind(sum_end);
70
71 sum_to.add_line_number(sum_entry, 10)
72 .add_line_number(sum_loop, 12)
73 .add_line_number(sum_done, 16)
74 .add_local_variable(sum_entry, sum_end, "limit", "I", {0})
75 .add_local_variable(sum_entry, sum_end, "sum", "I", {1})
76 .add_local_variable(sum_entry, sum_end, "index", "I", {2});
77
78 static_cast< void >(builder.add_method(
79 "sumTo",
80 "(I)I",
81 rpnx::cortado::method_access_flags::is_public |
82 rpnx::cortado::method_access_flags::is_static,
83 sum_to));
84 // [sum-to]
85
86 // [sparse-switch]
88 rpnx::cortado::label const zero_case = classify.new_label();
89 rpnx::cortado::label const one_case = classify.new_label();
90 rpnx::cortado::label const other_case = classify.new_label();
91
92 classify.iload({0}).lookupswitch(
93 other_case,
94 {{0, zero_case}, {1, one_case}});
95 classify.bind(zero_case)
96 .ldc_string("zero")
97 .append< rpnx::cortado::opcode::areturn >();
98 classify.bind(one_case)
99 .ldc_string("one")
100 .append< rpnx::cortado::opcode::areturn >();
101 classify.bind(other_case)
102 .ldc_string("many")
103 .append< rpnx::cortado::opcode::areturn >();
104
105 static_cast< void >(builder.add_method(
106 "classify",
107 "(I)Ljava/lang/String;",
108 rpnx::cortado::method_access_flags::is_public |
109 rpnx::cortado::method_access_flags::is_static,
110 classify));
111 // [sparse-switch]
112
113 // [exception-handler]
114 rpnx::cortado::code_builder safe_divide;
115 rpnx::cortado::label const protected_start = safe_divide.new_label();
116 rpnx::cortado::label const protected_end = safe_divide.new_label();
117 rpnx::cortado::label const arithmetic_handler = safe_divide.new_label();
118
119 safe_divide.bind(protected_start)
120 .iload({0})
121 .iload({1})
122 .append< rpnx::cortado::opcode::idiv >()
123 .append< rpnx::cortado::opcode::ireturn >()
124 .bind(protected_end)
125 .bind(arithmetic_handler)
126 .astore({2})
127 .append< rpnx::cortado::opcode::iconst_0 >()
128 .append< rpnx::cortado::opcode::ireturn >();
129 safe_divide.add_exception_handler(
130 protected_start,
131 protected_end,
132 arithmetic_handler,
133 "java/lang/ArithmeticException");
134
135 static_cast< void >(builder.add_method(
136 "safeDivide",
137 "(II)I",
138 rpnx::cortado::method_access_flags::is_public |
139 rpnx::cortado::method_access_flags::is_static,
140 safe_divide));
141 // [exception-handler]
142
143 // [reference-merge]
144 rpnx::cortado::code_builder select_collection;
145 rpnx::cortado::label const use_map = select_collection.new_label();
146 rpnx::cortado::label const collection_selected = select_collection.new_label();
147 select_collection.iload({0})
148 .branch< rpnx::cortado::opcode::ifeq >(use_map)
149 .aload({1})
150 .branch< rpnx::cortado::opcode::goto_ >(collection_selected)
151 .bind(use_map)
152 .aload({2})
153 .bind(collection_selected)
154 .append< rpnx::cortado::opcode::areturn >();
155
157 static_cast< void >(builder.add_method(
158 "selectCollection",
159 "(ZLjava/util/ArrayList;Ljava/util/HashMap;)Ljava/lang/Object;",
160 rpnx::cortado::method_access_flags::is_public |
161 rpnx::cortado::method_access_flags::is_static,
162 select_collection,
163 {},
165 // [reference-merge]
166
167 // [validate-and-serialize]
168 rpnx::cortado::class_file const generated_class = builder.build();
170 if (!report.valid())
171 {
172 throw std::runtime_error(report.diagnostics.front().message);
173 }
174 std::vector< std::byte > const class_bytes = rpnx::cortado::serialize_class_file(generated_class);
175 // [validate-and-serialize]
176
177 // [package-jar]
178 std::string const manifest_text = "Manifest-Version: 1.0\n\n";
179 std::vector< std::byte > manifest_bytes;
180 manifest_bytes.reserve(manifest_text.size());
181 for (char const character : manifest_text)
182 {
183 manifest_bytes.push_back(static_cast< std::byte >(static_cast< std::uint8_t >(character)));
184 }
185
187 archive.entries.push_back(rpnx::cortado::jar_entry{
188 .name = "META-INF/MANIFEST.MF",
189 .data = std::move(manifest_bytes),
190 .compression = rpnx::cortado::compression_method::stored,
191 .flags = rpnx::cortado::jar_entry_flags::utf8_names,
192 .modification_date = rpnx::cortado::zip_dos_date{0x0021}});
193 archive.entries.push_back(rpnx::cortado::jar_entry{
194 .name = "tutorial/Generated.class",
195 .data = class_bytes,
196 .compression = rpnx::cortado::compression_method::deflated,
197 .flags = rpnx::cortado::jar_entry_flags::utf8_names,
198 .modification_date = rpnx::cortado::zip_dos_date{0x0021}});
199 std::vector< std::byte > const jar_bytes = rpnx::cortado::serialize_jar(archive);
200 // [package-jar]
201
202 auto write_binary_file = [](std::filesystem::path const& path, std::span< std::byte const > bytes)
203 {
204 std::ofstream output(path, std::ios::binary | std::ios::trunc);
205 if (!output)
206 {
207 throw std::runtime_error("could not open " + path.string());
208 }
209 output.write(reinterpret_cast< char const* >(bytes.data()), static_cast< std::streamsize >(bytes.size()));
210 if (!output)
211 {
212 throw std::runtime_error("could not write " + path.string());
213 }
214 };
215
216 std::filesystem::path const package_directory = output_directory / "tutorial";
217 std::filesystem::create_directories(package_directory);
218 write_binary_file(package_directory / "Generated.class", class_bytes);
219 write_binary_file(output_directory / "tutorial-codegen.jar", jar_bytes);
220 return 0;
221}
222catch (std::exception const& error)
223{
224 std::cerr << error.what() << '\n';
225 return 1;
226}
Deterministic constant-pool and symbolic class-file builders.
An owning symbolic class-file builder with a shared constant-pool interner.
Definition builders.hpp:673
Non-owning type-erased class-hierarchy resolver used for reference merges.
Definition builders.hpp:286
An owning symbolic JVM code builder.
Definition builders.hpp:356
auto iload(local_variable_index index) -> code_builder &
Loads an int from a local using the shortest valid encoding.
auto aload(local_variable_index index) -> code_builder &
Loads a reference from a local using the shortest valid encoding.
auto bind(label value) -> code_builder &
Binds a label at the current instruction position.
label new_label()
Creates a fresh label owned by this builder.
auto add_exception_handler(label start, label end, label handler, std::optional< std::string > catch_type=std::nullopt) -> code_builder &
Adds a symbolic exception-table range.
auto add_line_number(label start, std::uint16_t line_number) -> code_builder &
Associates a source line with a symbolic code position.
JAR-compatible ZIP serialization and deserialization APIs.
std::vector< std::byte > serialize_jar(jar_file const &value)
Serializes an archive as a non-Zip64 JAR-compatible ZIP file.
std::vector< std::byte > serialize_class_file(class_file const &value, class_file_write_options const &options={})
Serializes a class file using the big-endian JVM class-file format.
validation_report validate_class_file(class_file const &value, validation_options const &options={})
Validates a complete class-file model without throwing for model errors.
An owning, typed representation of one JVM class file.
A deliberately conservative hierarchy resolver for the code-generation tutorial.
One owning JAR/ZIP entry, with its data stored uncompressed in memory.
Definition types.hpp:283
An owning JAR archive representation.
Definition types.hpp:299
std::vector< jar_entry > entries
Entries in central-directory order; duplicate names remain distinct.
Definition types.hpp:300
A symbolic bytecode position owned by one code_builder.
Definition builders.hpp:34
The complete bounded diagnostic result of one validation pass.
bool valid() const noexcept
Returns true when validation produced no diagnostics.
std::vector< validation_diagnostic > diagnostics
Failures in deterministic traversal order, bounded by the options.
A packed MS-DOS date value from a ZIP entry header.
Definition types.hpp:262
Bounded, non-throwing validation for structured class-file models.