Skip to content

Commit 2715f82

Browse files
committedApr 17, 2026
Stub iig tool (AI draft, works enough)
1 parent 418bd18 commit 2715f82

File tree

1 file changed

+552
-0
lines changed
  • Developer/Default.xctoolchain

1 file changed

+552
-0
lines changed
 
Lines changed: 552 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,552 @@
1+
#include <algorithm>
2+
#include <cctype>
3+
#include <cstdint>
4+
#include <cstdlib>
5+
#include <cstring>
6+
#include <fstream>
7+
#include <filesystem>
8+
#include <iostream>
9+
#include <regex>
10+
#include <sstream>
11+
#include <string>
12+
#include <vector>
13+
14+
namespace {
15+
16+
struct Options {
17+
std::string defPath;
18+
std::string headerPath;
19+
std::string implPath;
20+
};
21+
22+
struct MethodInfo {
23+
std::string returnType;
24+
std::string name;
25+
std::string args;
26+
bool isStatic{false};
27+
bool isVirtual{false};
28+
bool isLocalOnly{false};
29+
};
30+
31+
static void
32+
usage(const char *prog)
33+
{
34+
std::cerr
35+
<< "usage: " << prog << " --def <input.iig> --header <output.h> [--impl <output.cpp|/dev/null>] [-- ...cflags]\n"
36+
<< " " << prog << " <input.iig> -o <output.h>\n";
37+
}
38+
39+
static bool
40+
readFile(const std::string &path, std::string &out)
41+
{
42+
std::ifstream f(path);
43+
if (!f) {
44+
return false;
45+
}
46+
std::stringstream ss;
47+
ss << f.rdbuf();
48+
out = ss.str();
49+
return true;
50+
}
51+
52+
static bool
53+
writeFile(const std::string &path, const std::string &content)
54+
{
55+
std::ofstream f(path);
56+
if (!f) {
57+
return false;
58+
}
59+
f << content;
60+
return true;
61+
}
62+
63+
static std::string
64+
trim(const std::string &s)
65+
{
66+
size_t b = 0;
67+
while (b < s.size() && std::isspace(static_cast<unsigned char>(s[b]))) {
68+
b++;
69+
}
70+
size_t e = s.size();
71+
while (e > b && std::isspace(static_cast<unsigned char>(s[e - 1]))) {
72+
e--;
73+
}
74+
return s.substr(b, e - b);
75+
}
76+
77+
78+
static std::string
79+
rewriteIncludes(const std::string &input)
80+
{
81+
std::stringstream in(input);
82+
std::ostringstream out;
83+
std::string line;
84+
std::regex incl(R"(^\s*#\s*include\s*<([^>]+)\.iig>\s*$)");
85+
std::smatch m;
86+
while (std::getline(in, line)) {
87+
if (std::regex_match(line, m, incl)) {
88+
out << "#include <" << m[1].str() << ".h> /* .iig include */\n";
89+
} else {
90+
out << line << "\n";
91+
}
92+
}
93+
return out.str();
94+
}
95+
96+
static std::string
97+
removeExtendsClasses(const std::string &input)
98+
{
99+
std::string out = input;
100+
size_t p = 0;
101+
while (true) {
102+
size_t start = out.find("class EXTENDS", p);
103+
if (start == std::string::npos) {
104+
break;
105+
}
106+
size_t brace = out.find('{', start);
107+
if (brace == std::string::npos) {
108+
break;
109+
}
110+
int depth = 1;
111+
size_t i = brace + 1;
112+
for (; i < out.size() && depth > 0; i++) {
113+
if (out[i] == '{') {
114+
depth++;
115+
} else if (out[i] == '}') {
116+
depth--;
117+
}
118+
}
119+
if (depth != 0) {
120+
break;
121+
}
122+
size_t semi = out.find(';', i);
123+
if (semi == std::string::npos) {
124+
break;
125+
}
126+
out.replace(start, (semi + 1) - start, "/* EXTENDS class omitted by iig-linux */\n");
127+
p = start;
128+
}
129+
return out;
130+
}
131+
132+
static std::string
133+
sanitizeDocumentationText(const std::string &input)
134+
{
135+
std::string out = input;
136+
out = std::regex_replace(out, std::regex(R"(\bLOCALONLY\b)"), "");
137+
out = std::regex_replace(out, std::regex(R"(\bLOCAL\b)"), "");
138+
out = std::regex_replace(out, std::regex(R"(class\s+KERNEL\s+)"), "class ");
139+
out = std::regex_replace(out, std::regex(R"(\n\s*#\s*undef\s+KERNEL\s*\n)"), "\n");
140+
return out;
141+
}
142+
143+
static std::string
144+
sanitizeTypeText(std::string s)
145+
{
146+
s = std::regex_replace(s, std::regex(R"(\bLOCALONLY\b)"), "");
147+
s = std::regex_replace(s, std::regex(R"(\bLOCAL\b)"), "");
148+
s = std::regex_replace(s, std::regex(R"(\bKERNEL\b)"), "");
149+
s = std::regex_replace(s, std::regex(R"(\bTARGET\b)"), "");
150+
s = std::regex_replace(s, std::regex(R"(\s+)"), " ");
151+
return trim(s);
152+
}
153+
154+
static std::string
155+
escapeMacroContinuation(const std::string &s)
156+
{
157+
std::string out;
158+
out.reserve(s.size() + 8);
159+
for (char c : s) {
160+
if (c == '\\') {
161+
out += "\\\\";
162+
} else {
163+
out.push_back(c);
164+
}
165+
}
166+
return out;
167+
}
168+
169+
static uint64_t
170+
fnv1a64(const std::string &s)
171+
{
172+
uint64_t h = 14695981039346656037ULL;
173+
for (unsigned char c : s) {
174+
h ^= static_cast<uint64_t>(c);
175+
h *= 1099511628211ULL;
176+
}
177+
return h;
178+
}
179+
180+
static std::vector<std::string>
181+
splitArgs(const std::string &args)
182+
{
183+
std::vector<std::string> out;
184+
std::string cur;
185+
int angle = 0;
186+
int paren = 0;
187+
int bracket = 0;
188+
for (char c : args) {
189+
if (c == '<') {
190+
angle++;
191+
} else if (c == '>') {
192+
if (angle > 0) {
193+
angle--;
194+
}
195+
} else if (c == '(') {
196+
paren++;
197+
} else if (c == ')') {
198+
if (paren > 0) {
199+
paren--;
200+
}
201+
} else if (c == '[') {
202+
bracket++;
203+
} else if (c == ']') {
204+
if (bracket > 0) {
205+
bracket--;
206+
}
207+
}
208+
209+
if (c == ',' && angle == 0 && paren == 0 && bracket == 0) {
210+
std::string t = trim(cur);
211+
if (!t.empty()) {
212+
out.push_back(t);
213+
}
214+
cur.clear();
215+
continue;
216+
}
217+
cur.push_back(c);
218+
}
219+
std::string t = trim(cur);
220+
if (!t.empty() && t != "void") {
221+
out.push_back(t);
222+
}
223+
return out;
224+
}
225+
226+
static std::vector<MethodInfo>
227+
parseMethods(const std::string &classText, const std::string &className)
228+
{
229+
std::vector<MethodInfo> methods;
230+
std::string body = classText;
231+
body = std::regex_replace(body, std::regex(R"(/\*[\s\S]*?\*/)"), "");
232+
body = std::regex_replace(body, std::regex(R"(//.*?$)", std::regex::multiline), "");
233+
body = std::regex_replace(body, std::regex(R"(#pragma[^\n]*\n)"), "\n");
234+
235+
std::stringstream ss(body);
236+
std::string line;
237+
std::string stmt;
238+
int depth = 0;
239+
while (std::getline(ss, line)) {
240+
std::string t = trim(line);
241+
if (t.empty()) {
242+
continue;
243+
}
244+
for (char c : line) {
245+
if (c == '{') {
246+
depth++;
247+
} else if (c == '}') {
248+
depth--;
249+
}
250+
}
251+
if (depth < 1) {
252+
continue;
253+
}
254+
if (t == "public:" || t == "private:" || t == "protected:") {
255+
continue;
256+
}
257+
258+
stmt += " " + t;
259+
if (t.find(';') == std::string::npos) {
260+
continue;
261+
}
262+
263+
std::string cand = trim(stmt);
264+
stmt.clear();
265+
if (cand.empty() || cand.find('(') == std::string::npos) {
266+
continue;
267+
}
268+
if (cand.rfind("typedef ", 0) == 0) {
269+
continue;
270+
}
271+
272+
cand = cand.substr(0, cand.find(';'));
273+
274+
size_t lp = cand.find('(');
275+
size_t rp = cand.rfind(')');
276+
if (lp == std::string::npos || rp == std::string::npos || rp < lp) {
277+
continue;
278+
}
279+
std::string left = trim(cand.substr(0, lp));
280+
std::string args = trim(cand.substr(lp + 1, rp - lp - 1));
281+
282+
std::smatch m;
283+
std::regex nameRx(R"(([~A-Za-z_][A-Za-z0-9_]*)\s*$)");
284+
if (!std::regex_search(left, m, nameRx)) {
285+
continue;
286+
}
287+
std::string name = m[1].str();
288+
if (name == className || name == ("~" + className)) {
289+
continue;
290+
}
291+
left = trim(left.substr(0, m.position(1)));
292+
293+
MethodInfo mi;
294+
mi.isLocalOnly = (cand.find("LOCALONLY") != std::string::npos);
295+
mi.isStatic = (left.find("static ") != std::string::npos);
296+
mi.isVirtual = (left.find("virtual ") != std::string::npos);
297+
mi.name = name;
298+
mi.returnType = sanitizeTypeText(left);
299+
mi.args = sanitizeTypeText(args);
300+
if (mi.returnType.empty()) {
301+
continue;
302+
}
303+
methods.push_back(mi);
304+
}
305+
return methods;
306+
}
307+
308+
static std::string
309+
emitGeneratedMetadata(const std::string &className, const std::vector<MethodInfo> &methods)
310+
{
311+
std::ostringstream out;
312+
for (const auto &m : methods) {
313+
if (m.isLocalOnly) {
314+
continue;
315+
}
316+
std::ostringstream sig;
317+
sig << className << "::" << m.name << "(" << m.args << ")";
318+
uint64_t id = fnv1a64(sig.str());
319+
out << "#define " << className << "_" << m.name << "_ID 0x"
320+
<< std::hex << std::nouppercase << id << "ULL" << std::dec << "\n";
321+
}
322+
out << "\n";
323+
324+
for (const auto &m : methods) {
325+
if (m.isLocalOnly) {
326+
continue;
327+
}
328+
out << "#define " << className << "_" << m.name << "_Args \\\n";
329+
auto args = splitArgs(m.args);
330+
if (args.empty()) {
331+
out << "\n\n";
332+
continue;
333+
}
334+
for (size_t i = 0; i < args.size(); i++) {
335+
out << " " << args[i];
336+
if (i + 1 < args.size()) {
337+
out << ", \\\n";
338+
} else {
339+
out << "\n\n";
340+
}
341+
}
342+
}
343+
344+
out << "#define " << className << "_Methods \\\n\\\npublic:\\\n\\\n";
345+
out << " virtual kern_return_t\\\n"
346+
<< " Dispatch(const IORPC rpc) APPLE_KEXT_OVERRIDE;\\\n\\\n";
347+
out << " static kern_return_t\\\n"
348+
<< " _Dispatch(" << className << " * self, const IORPC rpc);\\\n\\\n";
349+
350+
for (const auto &m : methods) {
351+
out << " " << escapeMacroContinuation(m.returnType) << "\\\n"
352+
<< " " << m.name << "(\\\n";
353+
auto args = splitArgs(m.args);
354+
if (args.empty()) {
355+
out << ");\\\n\\\n";
356+
continue;
357+
}
358+
for (size_t i = 0; i < args.size(); i++) {
359+
out << " " << escapeMacroContinuation(args[i]);
360+
if (i + 1 < args.size()) {
361+
out << ",\\\n";
362+
} else {
363+
if (!m.isLocalOnly && !m.isStatic) {
364+
out << ",\\\n OSDispatchMethod supermethod = NULL);\\\n\\\n";
365+
} else {
366+
out << ");\\\n\\\n";
367+
}
368+
}
369+
}
370+
}
371+
372+
out << "#define " << className << "_KernelMethods\n";
373+
out << "#define " << className << "_VirtualMethods\n";
374+
return out.str();
375+
}
376+
377+
static bool
378+
findMainClass(const std::string &input,
379+
std::string &className,
380+
size_t &classStart,
381+
size_t &classEnd)
382+
{
383+
std::regex cls(R"(class\s+(?:KERNEL\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*:\s*public\s+[A-Za-z_][A-Za-z0-9_]*)");
384+
std::smatch m;
385+
std::string work = input;
386+
size_t searchOffset = 0;
387+
388+
while (std::regex_search(work, m, cls)) {
389+
size_t abs = searchOffset + static_cast<size_t>(m.position(0));
390+
if (input.compare(abs, 13, "class EXTENDS") == 0) {
391+
searchOffset = abs + 13;
392+
work = input.substr(searchOffset);
393+
continue;
394+
}
395+
className = m[1].str();
396+
classStart = abs;
397+
size_t brace = input.find('{', classStart);
398+
if (brace == std::string::npos) {
399+
return false;
400+
}
401+
int depth = 1;
402+
size_t i = brace + 1;
403+
for (; i < input.size() && depth > 0; i++) {
404+
if (input[i] == '{') {
405+
depth++;
406+
} else if (input[i] == '}') {
407+
depth--;
408+
}
409+
}
410+
if (depth != 0) {
411+
return false;
412+
}
413+
size_t semi = input.find(';', i);
414+
if (semi == std::string::npos) {
415+
return false;
416+
}
417+
classEnd = semi + 1;
418+
return true;
419+
}
420+
421+
return false;
422+
}
423+
424+
static std::string
425+
buildHeader(const std::string &input, const std::string &basename)
426+
{
427+
std::string transformed = rewriteIncludes(input);
428+
transformed = removeExtendsClasses(transformed);
429+
430+
std::string className;
431+
size_t classStart = 0;
432+
size_t classEnd = 0;
433+
if (!findMainClass(transformed, className, classStart, classEnd)) {
434+
return "/* iig(iig-linux) generated from " + basename + " */\n\n" + transformed;
435+
}
436+
437+
size_t guardEnd = transformed.rfind("#endif");
438+
if (guardEnd == std::string::npos || guardEnd < classEnd) {
439+
guardEnd = transformed.size();
440+
}
441+
442+
std::string prologue = transformed.substr(0, classStart);
443+
std::string classText = transformed.substr(classStart, classEnd - classStart);
444+
std::string epilogue = transformed.substr(guardEnd);
445+
446+
std::string classTextDoc = sanitizeDocumentationText(classText);
447+
std::vector<MethodInfo> methods = parseMethods(classTextDoc, className);
448+
449+
std::ostringstream out;
450+
out << "/* iig(iig-linux) generated from " << basename << " */\n\n";
451+
out << prologue;
452+
out << "\n/* source class " << className << " " << basename << " */\n\n";
453+
out << "#if __DOCUMENTATION__\n\n";
454+
out << classTextDoc << "\n\n";
455+
out << "#else /* __DOCUMENTATION__ */\n\n";
456+
out << "#if KERNEL\n";
457+
out << "#ifndef " << className << "_Methods\n#define " << className << "_Methods\n#endif\n";
458+
out << "#ifndef " << className << "_KernelMethods\n#define " << className << "_KernelMethods\n#endif\n";
459+
out << "#ifndef " << className << "_VirtualMethods\n#define " << className << "_VirtualMethods\n#endif\n";
460+
out << "#else /* !KERNEL */\n\n";
461+
out << "/* generated class " << className << " " << basename << " */\n\n";
462+
out << emitGeneratedMetadata(className, methods) << "\n";
463+
out << classTextDoc << "\n\n";
464+
out << "#endif /* !KERNEL */\n\n";
465+
out << "#endif /* !__DOCUMENTATION__ */\n\n";
466+
out << epilogue;
467+
return out.str();
468+
}
469+
470+
static bool
471+
parseArgs(int argc, const char **argv, Options &opts)
472+
{
473+
bool sawDoubleDash = false;
474+
for (int i = 1; i < argc; i++) {
475+
std::string a = argv[i];
476+
if (a == "--") {
477+
sawDoubleDash = true;
478+
continue;
479+
}
480+
if (sawDoubleDash) {
481+
continue;
482+
}
483+
if ((a == "--def") && (i + 1 < argc)) {
484+
opts.defPath = argv[++i];
485+
continue;
486+
}
487+
if ((a == "--header") && (i + 1 < argc)) {
488+
opts.headerPath = argv[++i];
489+
continue;
490+
}
491+
if ((a == "--impl") && (i + 1 < argc)) {
492+
opts.implPath = argv[++i];
493+
continue;
494+
}
495+
if ((a == "-o") && (i + 1 < argc)) {
496+
opts.headerPath = argv[++i];
497+
continue;
498+
}
499+
if ((a == "--xnu-root") && (i + 1 < argc)) {
500+
i++;
501+
continue;
502+
}
503+
if (a == "-h" || a == "--help") {
504+
return false;
505+
}
506+
if (!a.empty() && a[0] != '-' && opts.defPath.empty()) {
507+
opts.defPath = a;
508+
continue;
509+
}
510+
}
511+
512+
return !opts.defPath.empty() && !opts.headerPath.empty();
513+
}
514+
515+
} // namespace
516+
517+
int
518+
main(int argc, const char **argv)
519+
{
520+
Options opts;
521+
if (!parseArgs(argc, argv, opts)) {
522+
usage(argv[0]);
523+
return 1;
524+
}
525+
526+
std::string input;
527+
if (!readFile(opts.defPath, input)) {
528+
std::cerr << "iig-linux: failed to read input: " << opts.defPath << "\n";
529+
return 1;
530+
}
531+
532+
std::string base = std::filesystem::path(opts.defPath).filename().string();
533+
std::string header = buildHeader(input, base);
534+
535+
if (!writeFile(opts.headerPath, header)) {
536+
std::cerr << "iig-linux: failed to write header: " << opts.headerPath << "\n";
537+
return 1;
538+
}
539+
540+
if (!opts.implPath.empty() && opts.implPath != "/dev/null") {
541+
std::ostringstream impl;
542+
impl << "/* iig(iig-linux) generated from " << base << " */\n";
543+
impl << "/* implementation generation is intentionally minimal in iig-linux */\n";
544+
if (!writeFile(opts.implPath, impl.str())) {
545+
std::cerr << "iig-linux: failed to write impl: " << opts.implPath << "\n";
546+
return 1;
547+
}
548+
}
549+
550+
return 0;
551+
}
552+

0 commit comments

Comments
 (0)
Please sign in to comment.