diff options
author | Alexey Yerin <yyp@disroot.org> | 2023-05-28 14:10:05 +0300 |
---|---|---|
committer | Alexey Yerin <yyp@disroot.org> | 2023-05-28 14:15:22 +0300 |
commit | 16e730f540acfe6a0015464c81831cdfacaf107b (patch) | |
tree | a2aa04bf788dc16977fc6cda2ed874938c3c13c5 /cmd |
Initial commit
Diffstat (limited to 'cmd')
-rw-r--r-- | cmd/demo/main.ha | 58 | ||||
-rw-r--r-- | cmd/demo4/main.ha | 59 | ||||
-rw-r--r-- | cmd/hare-gi/context.ha | 129 | ||||
-rw-r--r-- | cmd/hare-gi/ctype.ha | 263 | ||||
-rw-r--r-- | cmd/hare-gi/emit.ha | 704 | ||||
-rw-r--r-- | cmd/hare-gi/ident.ha | 144 | ||||
-rw-r--r-- | cmd/hare-gi/main.ha | 148 | ||||
-rw-r--r-- | cmd/hare-gi/populate.ha | 62 | ||||
-rw-r--r-- | cmd/xmltree/main.ha | 104 |
9 files changed, 1671 insertions, 0 deletions
diff --git a/cmd/demo/main.ha b/cmd/demo/main.ha new file mode 100644 index 0000000..cf807c6 --- /dev/null +++ b/cmd/demo/main.ha @@ -0,0 +1,58 @@ +use gio; +use glib; +use gobject; +use gtk; +use os; +use rt; +use types::c; + +fn about_clicked(btn: *gtk::Button, data: *void) void = { + let dialog = gtk::about_dialog_new(); + const authors: []*c::char = [ + c::fromstr("Harriet?"), + null: *c::char, + ]; + gtk::about_dialog_set_license_type( + dialog: *gtk::AboutDialog, + gtk::License::MPL_2_0, + ); + gtk::about_dialog_set_authors( + dialog: *gtk::AboutDialog, + authors: *[*]*c::char: **c::char, + ); + gtk::about_dialog_set_website( + dialog: *gtk::AboutDialog, + c::fromstr("https://git.sr.ht/~yerinalexey/hare-gi"), + ); + + gtk::widget_show_all(dialog); +}; + +fn activate(app: *gio::Application, data: *void) void = { + const app = app: *gtk::Application; + + let button = gtk::button_new_with_label(c::fromstr("About")); + gtk::button_connect_clicked(button: *gtk::Button, &about_clicked, null); + + let headerbar = gtk::header_bar_new(); + gtk::header_bar_set_title(headerbar: *gtk::HeaderBar, c::fromstr("hare-gi demo")); + gtk::header_bar_set_show_close_button(headerbar: *gtk::HeaderBar, glib::TRUE); + gtk::header_bar_pack_end(headerbar: *gtk::HeaderBar, button); + + let window = gtk::application_window_new(app); + gtk::window_set_titlebar(window: *gtk::Window, headerbar); + gtk::widget_show_all(window); +}; + +export fn main() void = { + let app = gtk::application_new( + c::fromstr("hare_gi.example"), + gio::ApplicationFlags::FLAGS_NONE, + ): *gio::Application; + gio::application_connect_activate(app, &activate, null); + const status = gio::application_run( + app, rt::argc: int, rt::argv: **c::char, + ); + gobject::object_unref(app); + os::exit(status); +}; diff --git a/cmd/demo4/main.ha b/cmd/demo4/main.ha new file mode 100644 index 0000000..c46bed2 --- /dev/null +++ b/cmd/demo4/main.ha @@ -0,0 +1,59 @@ +use gio; +use glib; +use gobject; +use gtk4; +use os; +use rt; +use types::c; + +fn about_clicked(btn: *gtk4::Button, data: *void) void = { + let dialog = gtk4::about_dialog_new(); + const authors: []*c::char = [ + c::fromstr("Harriet?"), + null: *c::char, + ]; + gtk4::about_dialog_set_license_type( + dialog: *gtk4::AboutDialog, + gtk4::License::MPL_2_0, + ); + gtk4::about_dialog_set_authors( + dialog: *gtk4::AboutDialog, + authors: *[*]*c::char: **c::char, + ); + gtk4::about_dialog_set_website( + dialog: *gtk4::AboutDialog, + c::fromstr("https://git.sr.ht/~yerinalexey/hare-gi"), + ); + + gtk4::widget_show(dialog); +}; + +fn activate(app: *gio::Application, data: *void) void = { + const app = app: *gtk4::Application; + + let button = gtk4::button_new_with_label(c::fromstr("About")); + gtk4::button_connect_clicked(button: *gtk4::Button, &about_clicked, null); + + let headerbar = gtk4::header_bar_new(); + let title = gtk4::label_new(c::fromstr("hare-gi demo")); + gtk4::header_bar_set_title_widget(headerbar: *gtk4::HeaderBar, title); + gtk4::header_bar_set_show_title_buttons(headerbar: *gtk4::HeaderBar, glib::TRUE); + gtk4::header_bar_pack_end(headerbar: *gtk4::HeaderBar, button); + + let window = gtk4::application_window_new(app); + gtk4::window_set_titlebar(window: *gtk4::Window, headerbar); + gtk4::widget_show(window); +}; + +export fn main() void = { + let app = gtk4::application_new( + c::fromstr("hare_gi.example"), + gio::ApplicationFlags::FLAGS_NONE, + ): *gio::Application; + gio::application_connect_activate(app, &activate, null); + const status = gio::application_run( + app, rt::argc: int, rt::argv: **c::char, + ); + gobject::object_unref(app); + os::exit(status); +}; diff --git a/cmd/hare-gi/context.ha b/cmd/hare-gi/context.ha new file mode 100644 index 0000000..d4f47fa --- /dev/null +++ b/cmd/hare-gi/context.ha @@ -0,0 +1,129 @@ +use fmt; +use gir; +use io; +use os; +use strings; + +type namespace = struct { + gir::namespace, + includes: []gir::include, + + // C -> Hare type mappings + types: [](str, str), + // All imported modules. Populated during emit_empty() + imports: []ident, + // A list of exported symbols to make sure nothing is emitted twice + exports: []str, + + module: ident, + output_file: io::file, + output: io::handle, +}; +fn namespace_finish(ns: *namespace) void = { + gir::namespace_finish(ns); + if (ns.output_file != -1) { + io::close(ns.output_file)!; + }; + for (let i = 0z; i < len(ns.includes); i += 1) { + gir::include_finish(&ns.includes[i]); + }; + free(ns.includes); + for (let i = 0z; i < len(ns.types); i += 1) { + // types[i].0 is borrowed from GIR structures + free(ns.types[i].1); + }; + free(ns.types); + for (let i = 0z; i < len(ns.imports); i += 1) { + strings::freeall(ns.imports[i]); + }; + free(ns.exports); + free(ns.module); +}; + +type context = struct { + namespaces: []namespace, + current: *namespace, + stack: []str, + brief: bool, + + // Core modules + glib: *namespace, + gobject: *namespace, +}; +fn context_finish(ctx: *context) void = { + for (let i = 0z; i < len(ctx.namespaces); i += 1) { + namespace_finish(&ctx.namespaces[i]); + }; + free(ctx.namespaces); +}; + +fn add_repository(ctx: *context, repo: gir::repository) void = { + for (let i = 0z; i < len(repo.namespaces); i += 1) { + let new = namespace { + output_file = -1: io::file, + output = io::empty, + ... + }; + *(&new: *gir::namespace) = repo.namespaces[i]; + append(new.includes, repo.includes...); + append(ctx.namespaces, new); + }; +}; + +fn get_namespace(ctx: *context, name: str, version: str...) nullable *namespace = { + assert(len(version) <= 1); + if (len(version) == 1) { + for (let i = 0z; i < len(ctx.namespaces); i += 1) { + if (ctx.namespaces[i].name == name + && ctx.namespaces[i].version == version[0]) { + return &ctx.namespaces[i]; + }; + }; + } else { + for (let i = 0z; i < len(ctx.namespaces); i += 1) { + if (ctx.namespaces[i].name == name) { + return &ctx.namespaces[i]; + }; + }; + }; + return null; +}; + +fn add_import(ns: *namespace, module: ident) void = { + for (let i = 0z; i < len(ns.imports); i += 1) { + if (ident_eq(ns.imports[i], module)) { + return; + }; + }; + append(ns.imports, strings::dupall(module)); +}; + +fn lookup_type(ctx: *context, name: str) ((*namespace, str) | void) = { + for (let i = 0z; i < len(ctx.namespaces); i += 1) { + const ns = &ctx.namespaces[i]; + match (lookup_type_in_namespace(ns, name)) { + case let ident: str => + return (ns, ident); + case void => + yield; + }; + }; +}; + +fn lookup_type_in_namespace(ns: *namespace, name: str) (str | void) = { + for (let i = 0z; i < len(ns.types); i += 1) { + // NOTE: sometimes the requested type doesn't have a prefix + // (like Gtk from GtkWindow), the second check catches that + if (ns.types[i].0 == name || ns.types[i].1 == name) { + return ns.types[i].1; + }; + }; +}; + +fn push(ctx: *context, name: str) void = { + append(ctx.stack, name); +}; +fn pop(ctx: *context) void = { + assert(len(ctx.stack) > 0); + delete(ctx.stack[len(ctx.stack) - 1]); +}; diff --git a/cmd/hare-gi/ctype.ha b/cmd/hare-gi/ctype.ha new file mode 100644 index 0000000..f45dc9b --- /dev/null +++ b/cmd/hare-gi/ctype.ha @@ -0,0 +1,263 @@ +// The world's worst C parser + +use ascii; +use strings; +use strio; +use fmt; + +type ctype = ((cmodule, str) | str | cbuiltin | cpointer); +type cpointer = *ctype; +type cbuiltin = enum { + VOID, + INT, + UINT, + BOOL, + SIZE, + RUNE, + VALIST, + VOID_POINTER, + + CHAR, + UCHAR, + SHORT, + USHORT, + LONG, + ULONG, + SSIZE, + + I8, U8, + I16, U16, + I32, U32, + I64, U64, + F32, F64, +}; +type cmodule = enum { + LIBC, + GLIB, + GOBJECT, +}; + +fn ctype_finish(type_: ctype) void = { + match (type_) { + case let pointer: cpointer => + ctype_finish(*pointer); + free(pointer); + case let s: str => + free(s); + case => yield; + }; +}; + +fn cbuiltin_str(b: cbuiltin) const str = switch (b) { +case cbuiltin::VOID => yield "void"; +case cbuiltin::INT => yield "int"; +case cbuiltin::UINT => yield "uint"; +case cbuiltin::BOOL => yield "bool"; +case cbuiltin::SIZE => yield "size"; +case cbuiltin::RUNE => yield "rune"; +case cbuiltin::VALIST => yield "valist"; +case cbuiltin::VOID_POINTER => yield "*void"; + +case cbuiltin::CHAR => yield "c::char"; +case cbuiltin::UCHAR => yield "c::uchar"; +case cbuiltin::SSIZE => yield "c::ssize"; +case cbuiltin::SHORT => yield "c::short"; +case cbuiltin::USHORT => yield "c::ushort"; +case cbuiltin::LONG => yield "c::long"; +case cbuiltin::ULONG => yield "c::ulong"; + +case cbuiltin::I8 => yield "i8"; +case cbuiltin::U8 => yield "u8"; +case cbuiltin::I16 => yield "i16"; +case cbuiltin::U16 => yield "u16"; +case cbuiltin::I32 => yield "i32"; +case cbuiltin::U32 => yield "u32"; +case cbuiltin::I64 => yield "i64"; +case cbuiltin::U64 => yield "u64"; +case cbuiltin::F32 => yield "f32"; +case cbuiltin::F64 => yield "f64"; +}; + +fn cbuiltin_unsigned(b: cbuiltin) cbuiltin = switch (b) { +case cbuiltin::INT => yield cbuiltin::UINT; +case cbuiltin::CHAR => yield cbuiltin::UCHAR; +case cbuiltin::SHORT => yield cbuiltin::USHORT; +case cbuiltin::LONG => yield cbuiltin::ULONG; +case => abort(); +}; + +fn cbuiltin_needs_import(b: cbuiltin) bool = switch (b) { +case cbuiltin::CHAR, cbuiltin::UCHAR, cbuiltin::SHORT, cbuiltin::USHORT, +cbuiltin::LONG, cbuiltin::ULONG, cbuiltin::SSIZE => yield true; +case => yield false; +}; + +let map: [](str, ctype) = [ + ("void", cbuiltin::VOID), + ("int", cbuiltin::INT), + ("char", cbuiltin::CHAR), + ("short", cbuiltin::SHORT), + ("long", cbuiltin::LONG), + ("float", cbuiltin::F32), + ("double", cbuiltin::F64), + + // C11, <stdbool.h> + ("_Bool", cbuiltin::BOOL), + ("bool", cbuiltin::BOOL), + + // <stdarg.h> + ("va_list", cbuiltin::VALIST), + + // <stdlib.h> + ("size_t", cbuiltin::SIZE), + ("ssize_t", cbuiltin::SSIZE), + + // <stdint.h> + ("int8_t", cbuiltin::I8), ("uint8_t", cbuiltin::U8), + ("int16_t", cbuiltin::I16), ("uint16_t", cbuiltin::U16), + ("int32_t", cbuiltin::I32), ("uint32_t", cbuiltin::U32), + ("int64_t", cbuiltin::I64), ("uint64_t", cbuiltin::U64), + + // <glib/gtypes.h> + ("gchar", cbuiltin::CHAR), + ("gshort", cbuiltin::SHORT), + ("glong", cbuiltin::LONG), + ("gint", cbuiltin::INT), + ("gboolean", (cmodule::GLIB, "boolean")), + ("guchar", cbuiltin::UCHAR), + ("gushort", cbuiltin::USHORT), + ("gulong", cbuiltin::ULONG), + ("guint", cbuiltin::UINT), + ("gfloat", cbuiltin::F32), + ("gdouble", cbuiltin::F64), + ("gpointer", cbuiltin::VOID_POINTER), + ("gconstpointer", cbuiltin::VOID_POINTER), + ("grefcount", cbuiltin::UINT), + ("gatomicrefcount", cbuiltin::UINT), + + ("gint8", cbuiltin::I8), ("guint8", cbuiltin::U8), + ("gint16", cbuiltin::I16), ("guint16", cbuiltin::U16), + ("gint32", cbuiltin::I32), ("guint32", cbuiltin::U32), + ("gint64", cbuiltin::I64), ("guint64", cbuiltin::U64), + ("gsize", cbuiltin::SIZE), ("gssize", cbuiltin::SSIZE), + ("goffset", cbuiltin::I64), + + // <glib/gunicode.h> + ("gunichar", cbuiltin::RUNE), + ("gunichar2", cbuiltin::U16), + + // <gobject/gvalue.h> + ("_Value__data__union", (cmodule::GOBJECT, "ValueUnion")), + + // <unistd.h> + ("pid_t", cbuiltin::UINT), + ("uid_t", cbuiltin::UINT), + + // <time.h> + ("time_t", (cmodule::LIBC, "time_t")), + ("tm", (cmodule::LIBC, "tm")), + + // <stdio.h> + ("FILE", (cmodule::LIBC, "FILE")), + + // <pwd.h> + ("passwd", (cmodule::LIBC, "passwd")), + + // freetype + ("int32", cbuiltin::I32), + + // ("utf8", ...) - see resolve() +]; + +fn parse_ctype(type_: str) ctype = { + let iter = strings::iter(type_); + let current: ctype = ""; + + let unsigned = false; + for (true) match (next_token(&iter)) { + case let s: str => + if (s == "const" || s == "volatile") { + continue; + }; + if (s == "unsigned") { + unsigned = true; + } else { + current = resolve(s); + if (unsigned) { + current = cbuiltin_unsigned(current as cbuiltin); + unsigned = false; + }; + }; + case let r: rune => + switch (r) { + case '*' => + current = alloc(current): cpointer: ctype; + case => abort(); + }; + case => + break; + }; + if (unsigned) { + return cbuiltin::UINT; + }; + return current; +}; + +fn resolve(s: str) ctype = { + if (s == "utf8") { + return alloc(cbuiltin::CHAR: ctype): cpointer; + }; + for (let i = 0z; i < len(map); i += 1) { + if (map[i].0 == s) { + return map[i].1; + }; + }; + return s; +}; + +fn next_token(iter: *strings::iterator) (str | rune | void) = { + for (true) match (_next_token(iter)) { + case let s: str => + return s; + case let r: rune => + return r; + case => + break; + }; +}; + +fn _next_token(iter: *strings::iterator) (str | rune | void) = { + let buffer = strio::dynamic(); + let first = true; + for (true) match (strings::next(iter)) { + case let r: rune => + if (ascii::isspace(r)) { + if (len(buffer.buf) > 0) { + return strio::string(&buffer); + }; + continue; + }; + if (is_ident(r, first)) { + strio::appendrune(&buffer, r)!; + first = false; + } else switch (r) { + case '*' => + if (len(buffer.buf) > 0) { + strings::prev(iter); + return strio::string(&buffer); + } else { + return r; + }; + case => + fmt::fatalf("Unexpected character in a type: '{}'", r); + }; + case => + break; + }; + if (len(buffer.buf) > 0) { + return strio::string(&buffer); + }; +}; + +fn is_ident(r: rune, first: bool) bool + = ascii::isalpha(r) || r == '_' || (!first && ascii::isdigit(r)); diff --git a/cmd/hare-gi/emit.ha b/cmd/hare-gi/emit.ha new file mode 100644 index 0000000..fd37110 --- /dev/null +++ b/cmd/hare-gi/emit.ha @@ -0,0 +1,704 @@ +use ascii; +use bufio; +use fmt; +use gir; +use io; +use os; +use strings; + +// Second stage of codegen: generate code for every namespace, but discard it. +// This will fill namespace.imports list, which will be used in the next stage. +fn emit_empty(ctx: *context) void = { + for (let i = 0z; i < len(ctx.namespaces); i += 1) { + const ns = &ctx.namespaces[i]; + ctx.current = ns; + ctx.current.output = io::empty; + emit_namespace(ctx, ns)!; + }; +}; + +// Third stage: generate the final code for modules that have specified an +// output file. +fn emit(ctx: *context) (void | io::error) = { + for (let i = 0z; i < len(ctx.namespaces); i += 1) { + const ns = &ctx.namespaces[i]; + + ctx.current = ns; + + if (ns.output_file == -1) continue; + + static let wbuf: [os::BUFSIZ]u8 = [0...]; + const stream = bufio::buffered(ns.output_file, [], wbuf); + ctx.current.output = &stream; + + delete(ctx.current.exports[..]); + emit_namespace(ctx, ns)?; + }; +}; + +fn emit_namespace(ctx: *context, ns: *namespace) (void | io::error) = { + defer assert(len(ctx.stack) == 0); + + for (let i = 0z; i < len(ns.imports); i += 1) { + fmt::fprint(ctx.current.output, "use ")?; + emit_ident(ctx.current.output, ns.imports[i])?; + fmt::fprintln(ctx.current.output, ";")?; + }; + + for (let i = 0z; i < len(ns.aliases); i += 1) { + emit_alias(ctx, &ns.aliases[i])?; + }; + for (let i = 0z; i < len(ns.classes); i += 1) { + emit_class(ctx, &ns.classes[i])?; + }; + for (let i = 0z; i < len(ns.interfaces); i += 1) { + emit_interface(ctx, &ns.interfaces[i])?; + }; + for (let i = 0z; i < len(ns.records); i += 1) { + emit_record(ctx, &ns.records[i])?; + }; + for (let i = 0z; i < len(ns.enums); i += 1) { + emit_enumeration(ctx, &ns.enums[i])?; + }; + for (let i = 0z; i < len(ns.functions); i += 1) { + emit_function(ctx, &ns.functions[i])?; + }; + for (let i = 0z; i < len(ns.unions); i += 1) { + emit_union(ctx, &ns.unions[i])?; + }; + for (let i = 0z; i < len(ns.bitfields); i += 1) { + emit_bitfield(ctx, &ns.bitfields[i])?; + }; + for (let i = 0z; i < len(ns.callbacks); i += 1) { + emit_callback(ctx, &ns.callbacks[i])?; + }; + for (let i = 0z; i < len(ns.constants); i += 1) { + emit_constant(ctx, &ns.constants[i])?; + }; +}; + +fn emit_alias(ctx: *context, alias: *gir::alias) (void | io::error) = { + if (is_exported(ctx, alias.c_type)) { + return; + }; + append(ctx.current.exports, alias.c_type); + + emit_doc(ctx, alias)?; + fmt::fprintf(ctx.current.output, "export type {} = ", + fix_identifier(alias.name))?; + emit_type(ctx, alias.inner)?; + fmt::fprintln(ctx.current.output, ";")?; +}; + +fn emit_class(ctx: *context, class: *gir::class) (void | io::error) = { + if (is_exported(ctx, class.c_type)) { + return; + }; + append(ctx.current.exports, class.c_type); + + emit_doc(ctx, class)?; + fmt::fprintf(ctx.current.output, "export type {} = ", + fix_identifier(class.name))?; + if (len(class.entries) == 0) { + // opaque + fmt::fprintln(ctx.current.output, "*void;")?; + } else { + fmt::fprintln(ctx.current.output, "struct {")?; + for (let i = 0z; i < len(class.entries); i += 1) { + emit_entry(ctx, &class.entries[i])?; + }; + fmt::fprintln(ctx.current.output, "};")?; + }; + + // TODO: add implements into documentation + + push(ctx, class.name); + for (let i = 0z; i < len(class.constructors); i += 1) { + emit_constructor(ctx, &class.constructors[i])?; + }; + for (let i = 0z; i < len(class.methods); i += 1) { + emit_method(ctx, &class.methods[i])?; + }; + for (let i = 0z; i < len(class.functions); i += 1) { + emit_function(ctx, &class.functions[i])?; + }; + // Ignore: class.virtual_methods + // Ignore: class.properties + for (let i = 0z; i < len(class.signals); i += 1) { + emit_signal(ctx, &class.signals[i])?; + }; + for (let i = 0z; i < len(class.constants); i += 1) { + emit_constant(ctx, &class.constants[i])?; + }; + // Unused: class.callbacks + assert(len(class.callbacks) == 0); + pop(ctx); +}; + +fn emit_interface(ctx: *context, iface: *gir::interface) (void | io::error) = { + if (is_exported(ctx, iface.c_type)) { + return; + }; + append(ctx.current.exports, iface.c_type); + + emit_doc(ctx, iface)?; + fmt::fprintf(ctx.current.output, "export type {} = ", + fix_identifier(iface.name))?; + if (len(iface.entries) == 0) { + // opaque + fmt::fprintln(ctx.current.output, "*void;")?; + } else { + fmt::fprintln(ctx.current.output, "struct {")?; + for (let i = 0z; i < len(iface.entries); i += 1) { + emit_entry(ctx, &iface.entries[i])?; + }; + fmt::fprintln(ctx.current.output, "};")?; + }; + + // TODO: add implements and prerequisites into documentation + + push(ctx, iface.name); + for (let i = 0z; i < len(iface.constructors); i += 1) { + emit_constructor(ctx, &iface.constructors[i])?; + }; + for (let i = 0z; i < len(iface.methods); i += 1) { + emit_method(ctx, &iface.methods[i])?; + }; + for (let i = 0z; i < len(iface.functions); i += 1) { + emit_function(ctx, &iface.functions[i])?; + }; + // Ignore: iface.virtual_methods + // Ignore: iface.properties + for (let i = 0z; i < len(iface.signals); i += 1) { + emit_signal(ctx, &iface.signals[i])?; + }; + for (let i = 0z; i < len(iface.constants); i += 1) { + emit_constant(ctx, &iface.constants[i])?; + }; + // Unused: iface.callbacks + assert(len(iface.callbacks) == 0); + pop(ctx); +}; + +fn emit_union(ctx: *context, union_: *gir::union_) (void | io::error) = { + if (is_exported(ctx, union_.c_type)) { + return; + }; + append(ctx.current.exports, union_.c_type); + + emit_doc(ctx, union_)?; + fmt::fprintf(ctx.current.output, "export type {} = ", + fix_identifier(union_.name))?; + if (len(union_.entries) == 0) { + fmt::fprintln(ctx.current.output, "void;")?; // FIXME + } else { + fmt::fprintln(ctx.current.output, "union {")?; + for (let i = 0z; i < len(union_.entries); i += 1) { + emit_entry(ctx, &union_.entries[i])?; + }; + fmt::fprintln(ctx.current.output, "};")?; + }; + + push(ctx, union_.name); + for (let i = 0z; i < len(union_.constructors); i += 1) { + emit_constructor(ctx, &union_.constructors[i])?; + }; + for (let i = 0z; i < len(union_.methods); i += 1) { + emit_method(ctx, &union_.methods[i])?; + }; + for (let i = 0z; i < len(union_.functions); i += 1) { + emit_function(ctx, &union_.functions[i])?; + }; + pop(ctx); +}; + +fn emit_record(ctx: *context, record: *gir::record) (void | io::error) = { + if (is_exported(ctx, record.c_type)) { + return; + }; + append(ctx.current.exports, record.c_type); + + emit_doc(ctx, record)?; + fmt::fprintf(ctx.current.output, "export type {} = ", + fix_identifier(record.name))?; + if (record.opaque || len(record.entries) == 0) { + fmt::fprintln(ctx.current.output, "*void;")?; + } else { + fmt::fprintln(ctx.current.output, "struct {")?; + for (let i = 0z; i < len(record.entries); i += 1) { + emit_entry(ctx, &record.entries[i])?; + }; + fmt::fprintln(ctx.current.output, "};")?; + }; + + push(ctx, record.name); + for (let i = 0z; i < len(record.constructors); i += 1) { + emit_constructor(ctx, &record.constructors[i])?; + }; + for (let i = 0z; i < len(record.methods); i += 1) { + emit_method(ctx, &record.methods[i])?; + }; + for (let i = 0z; i < len(record.functions); i += 1) { + emit_function(ctx, &record.functions[i])?; + }; + pop(ctx); +}; + +fn emit_enumeration( + ctx: *context, + enumeration: *gir::enumeration, +) (void | io::error) = { + if (is_exported(ctx, enumeration.c_type)) { + return; + }; + append(ctx.current.exports, enumeration.c_type); + + emit_doc(ctx, enumeration)?; + fmt::fprintfln(ctx.current.output, "export type {} = enum uint {{", + fix_identifier(enumeration.name))?; + for (let i = 0z; i < len(enumeration.members); i += 1) { + emit_member(ctx, &enumeration.members[i])?; + }; + fmt::fprintln(ctx.current.output, "};")?; + + push(ctx, enumeration.name); + for (let i = 0z; i < len(enumeration.functions); i += 1) { + emit_function(ctx, &enumeration.functions[i])?; + }; + pop(ctx); +}; + +fn emit_bitfield(ctx: *context, bitfield: *gir::bitfield) (void | io::error) = { + if (is_exported(ctx, bitfield.c_type)) { + return; + }; + append(ctx.current.exports, bitfield.c_type); + + emit_doc(ctx, bitfield)?; + fmt::fprintfln(ctx.current.output, "export type {} = enum uint {{", + fix_identifier(bitfield.name))?; + for (let i = 0z; i < len(bitfield.members); i += 1) { + emit_member(ctx, &bitfield.members[i])?; + }; + fmt::fprintln(ctx.current.output, "};")?; + + push(ctx, bitfield.name); + for (let i = 0z; i < len(bitfield.functions); i += 1) { + emit_function(ctx, &bitfield.functions[i])?; + }; + pop(ctx); +}; + +fn emit_member(ctx: *context, member: *gir::member) (void | io::error) = { + emit_indented_doc(ctx, member)?; + fmt::fprintfln(ctx.current.output, "\t" "{} = {},", + to_uppercase(fix_identifier(member.name)), member.value)?; +}; + +fn emit_callback(ctx: *context, callback: *gir::callback) (void | io::error) = { + if (is_exported(ctx, callback.c_type)) { + return; + }; + append(ctx.current.exports, callback.c_type); + + emit_doc(ctx, callback)?; + fmt::fprintf(ctx.current.output, "export type {} = *fn(", + fix_identifier(callback.name))?; + emit_params(ctx, callback.params)?; + if (callback.throws) { + if (len(callback.params) > 0) { + fmt::fprint(ctx.current.output, ", ")?; + }; + fmt::fprint(ctx.current.output, "error: nullable **")?; + emit_object(ctx, ctx.glib, "Error")?; + }; + fmt::fprint(ctx.current.output, ") ")?; + emit_return_value(ctx, callback.return_value)?; + fmt::fprintln(ctx.current.output, ";")?; +}; + +fn emit_constant(ctx: *context, constant: *gir::constant) (void | io::error) = { + // TODO: constants + return; +}; + +fn emit_constructor( + ctx: *context, + constructor: *gir::constructor, +) (void | io::error) = { + if (is_exported(ctx, constructor.c_identifier)) { + return; + }; + append(ctx.current.exports, constructor.c_identifier); + + emit_doc(ctx, constructor)?; + fmt::fprintf(ctx.current.output, `export @symbol("{}") fn `, + constructor.c_identifier)?; + emit_function_name(ctx, constructor.name)?; + fmt::fprint(ctx.current.output, "(")?; + emit_params(ctx, constructor.params)?; + if (constructor.throws) { + if (len(constructor.params) > 0) { + fmt::fprint(ctx.current.output, ", ")?; + }; + fmt::fprint(ctx.current.output, "error: nullable **")?; + emit_object(ctx, ctx.glib, "Error")?; + }; + fmt::fprint(ctx.current.output, ") ")?; + emit_return_value(ctx, constructor.return_value)?; + fmt::fprintln(ctx.current.output, ";")?; +}; + +fn emit_method(ctx: *context, method: *gir::method) (void | io::error) = { + if (is_exported(ctx, method.c_identifier)) { + return; + }; + append(ctx.current.exports, method.c_identifier); + + emit_doc(ctx, method)?; + fmt::fprintf(ctx.current.output, `export @symbol("{}") fn `, + method.c_identifier)?; + emit_function_name(ctx, method.name)?; + fmt::fprint(ctx.current.output, "(")?; + emit_instance_param(ctx, method.instance)?; + if (len(method.params) > 0) { + fmt::fprint(ctx.current.output, ", ")?; + }; + emit_params(ctx, method.params)?; + if (method.throws) { + fmt::fprint(ctx.current.output, ", error: nullable **")?; + emit_object(ctx, ctx.glib, "Error")?; + }; + fmt::fprint(ctx.current.output, ") ")?; + emit_return_value(ctx, method.return_value)?; + fmt::fprintln(ctx.current.output, ";")?; +}; + +fn emit_function(ctx: *context, function: *gir::function) (void | io::error) = { + if (is_exported(ctx, function.c_identifier)) { + return; + }; + append(ctx.current.exports, function.c_identifier); + + emit_doc(ctx, function)?; + fmt::fprintf(ctx.current.output, `export @symbol("{}") fn `, + function.c_identifier)?; + emit_function_name(ctx, function.name)?; + fmt::fprint(ctx.current.output, "(")?; + emit_params(ctx, function.params)?; + if (function.throws) { + if (len(function.params) > 0) { + fmt::fprint(ctx.current.output, ", ")?; + }; + fmt::fprint(ctx.current.output, "error: nullable **")?; + emit_object(ctx, ctx.glib, "Error")?; + }; + fmt::fprint(ctx.current.output, ") ")?; + emit_return_value(ctx, function.return_value)?; + fmt::fprintln(ctx.current.output, ";")?; +}; + +fn emit_signal(ctx: *context, signal: *gir::signal) (void | io::error) = { + assert(len(ctx.stack) >= 1); + // Example code for GtkWindow::activate: + // + // export fn window_connect_activate( + // instance: *gtk::Window, + // handler: *fn(instance: *gtk::Window, data: *void) void, + // data: nullable *void, + // ) u64 = gobject::signal_connect_data( + // instance, + // *(&"activate\0": []u8): *[*]u8: *c::char, + // handler: gobject::Callback, + // data: *void, + // null: gobject::ClosureNotify, 0, + // ); + + // TODO: add documentation + + const function = strings::concat( + "connect_", + normalize_signal(signal.name), + ); + defer free(function); + + fmt::fprintf(ctx.current.output, "export fn ")?; + emit_function_name(ctx, function)?; + fmt::fprintln(ctx.current.output, "(")?; + fmt::fprintfln(ctx.current.output, "\t" "instance: *{},", ctx.stack[0])?; + fmt::fprintf(ctx.current.output, + "\t" "handler: *fn(instance: *{}", + ctx.stack[0], + )?; + if (len(signal.params) > 0) { + fmt::fprint(ctx.current.output, ", ")?; + emit_params(ctx, signal.params)?; + }; + fmt::fprint(ctx.current.output, ", data: *void) ")?; + emit_return_value(ctx, signal.return_value)?; + fmt::fprintln(ctx.current.output, ",")?; + fmt::fprintln(ctx.current.output, "\t" "data: nullable *void,")?; + fmt::fprint(ctx.current.output, ") u64 = ")?; + emit_object(ctx, ctx.gobject, "signal_connect_data")?; + fmt::fprintln(ctx.current.output, "(")?; + fmt::fprintln(ctx.current.output, "\t" "instance,")?; + fmt::fprintfln(ctx.current.output, + "\t" `*(&"{}\0": *[]u8): *[*]u8: *c::char,`, + signal.name, + )?; + fmt::fprint(ctx.current.output, "\t" "handler: ")?; + emit_object(ctx, ctx.gobject, "Callback")?; + fmt::fprintln(ctx.current.output, ",")?; + fmt::fprintln(ctx.current.output, "\t" "data: *void,")?; + fmt::fprint(ctx.current.output, "\t" "null: ")?; + emit_object(ctx, ctx.gobject, "ClosureNotify")?; + fmt::fprintln(ctx.current.output, ", 0,")?; + fmt::fprintln(ctx.current.output, ");")?; +}; + +fn emit_instance_param( + ctx: *context, + instance: gir::instance_parameter, +) (void | io::error) = { + fmt::fprintf(ctx.current.output, "{}: ", fix_identifier(instance.name))?; + emit_type(ctx, instance.type_)?; +}; + +fn emit_params(ctx: *context, params: []gir::callable_param) (void | io::error) = { + for (let i = 0z; i < len(params); i += 1) { + const param = params[i]; + if (i > 0) { + fmt::fprint(ctx.current.output, ", ")?; + }; + match (param.parameter) { + case let t: gir::any_type => + fmt::fprintf(ctx.current.output, "{}: ", + fix_identifier(param.name))?; + emit_type(ctx, t)?; + case gir::varargs => + fmt::fprint(ctx.current.output, "...")?; + assert(i == len(params) - 1); + }; + }; +}; + +fn emit_return_value( + ctx: *context, + ret: (gir::callable_return | void), +) (void | io::error) = { + match (ret) { + case let ret: gir::callable_return => + emit_type(ctx, ret.type_)?; + case => + fmt::fprint(ctx.current.output, "void")?; + }; +}; + +fn emit_function_name(ctx: *context, name: str) (void | io::error) = { + if (len(ctx.stack) == 0) { + fmt::fprint(ctx.current.output, fix_identifier(name))?; + return; + }; + + const path = strings::concat(ctx.stack...); + defer free(path); + const lower = swap_case(path); + defer free(lower); + + fmt::fprintf(ctx.current.output, "{}_{}", lower, name)?; +}; + +fn emit_entry(ctx: *context, entry: *gir::entry) (void | io::error) = { + match (*entry) { + case let f: gir::field => + emit_field(ctx, &f)?; + case let u: gir::union_ => + emit_inline_union(ctx, &u)?; + case let r: gir::record => + emit_inline_record(ctx, &r)?; + }; +}; + +fn emit_field(ctx: *context, field: *gir::field) (void | io::error) = { + emit_indented_doc(ctx, field)?; + fmt::fprintf(ctx.current.output, "\t" "{}: ", + fix_identifier(field.name))?; + if (!field.writable) { + fmt::fprint(ctx.current.output, "const ")?; + }; + emit_type(ctx, field.type_)?; + fmt::fprintln(ctx.current.output, ",")?; +}; + +fn emit_inline_union(ctx: *context, union_: *gir::union_) (void | io::error) = { + emit_indented_doc(ctx, union_)?; + fmt::fprint(ctx.current.output, "\t")?; + if (len(union_.name) > 0) { + fmt::fprintf(ctx.current.output, "{}: ", + fix_identifier(union_.name))?; + }; + fmt::fprintln(ctx.current.output, "union {")?; + for (let i = 0z; i < len(union_.entries); i += 1) { + emit_entry(ctx, &union_.entries[i])?; + }; + fmt::fprintln(ctx.current.output, "},")?; +}; + +fn emit_inline_record(ctx: *context, record: *gir::record) (void | io::error) = { + emit_indented_doc(ctx, record)?; + fmt::fprint(ctx.current.output, "\t")?; + if (len(record.name) > 0) { + fmt::fprintf(ctx.current.output, "{}: ", + fix_identifier(record.name))?; + }; + fmt::fprintln(ctx.current.output, "struct {")?; + for (let i = 0z; i < len(record.entries); i += 1) { + emit_entry(ctx, &record.entries[i])?; + }; + fmt::fprintln(ctx.current.output, "},")?; +}; + +fn emit_indented_doc(ctx: *context, item: *gir::documentation) (void | io::error) = { + if (ctx.brief) { + return; + }; + const iter = strings::tokenize(item.doc, "\n"); + for (true) match (strings::next_token(&iter)) { + case let line: str => + fmt::fprintln(ctx.current.output, "\t" "//", line)?; + case => + break; + }; +}; + +fn emit_doc(ctx: *context, item: *gir::documentation) (void | io::error) = { + if (ctx.brief) { + return; + }; + const iter = strings::tokenize(item.doc, "\n"); + for (true) match (strings::next_token(&iter)) { + case let line: str => + fmt::fprintln(ctx.current.output, "//", line)?; + case => + break; + }; +}; + +fn emit_type(ctx: *context, t: (gir::any_type | gir::callback)) (void | io::error) = { + match (t) { + case let t: gir::simple_type => + if (len(t.c_type) > 0) { + const parsed = parse_ctype(t.c_type); + defer ctype_finish(parsed); + return emit_c_type(ctx, parsed); + } else { + const (first, second) = strings::cut(t.name, "."); + if (len(second) == 0) { + const parsed = parse_ctype(t.name); + defer ctype_finish(parsed); + return emit_c_type(ctx, parsed); + } else { + const ns = match (get_namespace(ctx, first)) { + case let ns: *namespace => + yield ns; + case null => + fmt::fprintf(ctx.current.output, + "#unresolved type {}#", t.name)?; + return; + }; + match (lookup_type_in_namespace(ns, second)) { + case let id: str => + emit_object(ctx, ns, id)?; + case => + fmt::fprintf(ctx.current.output, + "#unresolved type {}#", t.name)?; + }; + }; + }; + case let t: gir::array_type => + if (t.fixed_size == 0) { + if (len(t.c_type) > 0) { + const parsed = parse_ctype(t.c_type); + defer ctype_finish(parsed); + return emit_c_type(ctx, parsed); + } else { + fmt::fprint(ctx.current.output, "*")?; + return emit_type(ctx, *t.inner)?; + }; + } else { + fmt::fprintf(ctx.current.output, "[{}]", t.fixed_size)?; + return emit_type(ctx, *t.inner)?; + }; + case let t: gir::callback => + return emit_callback_type(ctx, &t); + }; +}; + +fn emit_callback_type(ctx: *context, cb: *gir::callback) (void | io::error) = { + fmt::fprint(ctx.current.output, "*fn(")?; + emit_params(ctx, cb.params)?; + if (cb.throws) { + if (len(cb.params) > 0) { + fmt::fprint(ctx.current.output, ", ")?; + }; + fmt::fprint(ctx.current.output, "error: nullable **")?; + emit_object(ctx, ctx.glib, "Error")?; + }; + fmt::fprint(ctx.current.output, ") ")?; + emit_return_value(ctx, cb.return_value)?; +}; + +fn emit_c_type(ctx: *context, type_: ctype) (void | io::error) = { + match (type_) { + case let special: (cmodule, str) => + const (mod, id) = special; + switch (mod) { + case cmodule::LIBC => + add_import(ctx.current, ["types", "libc"]); + fmt::fprintf(ctx.current.output, "libc::{}", id)?; + case cmodule::GLIB => + emit_object(ctx, ctx.glib, id)?; + case cmodule::GOBJECT => + emit_object(ctx, ctx.gobject, id)?; + }; + case let id: str => + match (lookup_type(ctx, id)) { + case let pair: (*namespace, str) => + emit_object(ctx, pair.0, pair.1)?; + case => + fmt::fprintf(ctx.current.output, "#unresolved type {}#", + id)?; + }; + case let b: cbuiltin => + if (cbuiltin_needs_import(b)) { + add_import(ctx.current, ["types", "c"]); + }; + fmt::fprint(ctx.current.output, cbuiltin_str(b))?; + case let p: cpointer => + fmt::fprint(ctx.current.output, '*')?; + return emit_c_type(ctx, *p); + }; +}; + +fn emit_object( + ctx: *context, + source: *namespace, + object: str, +) (void | io::error) = { + if (ident_eq(source.module, ctx.current.module)) { + fmt::fprint(ctx.current.output, object)?; + } else { + add_import(ctx.current, source.module); + fmt::fprintf(ctx.current.output, "{}::{}", + source.module[len(source.module) - 1], object)?; + }; +}; + +fn is_exported(ctx: *context, symbol: str) bool = { + for (let i = 0z; i < len(ctx.current.exports); i += 1) { + if (ctx.current.exports[i] == symbol) { + return true; + }; + }; + return false; +}; diff --git a/cmd/hare-gi/ident.ha b/cmd/hare-gi/ident.ha new file mode 100644 index 0000000..962a6b3 --- /dev/null +++ b/cmd/hare-gi/ident.ha @@ -0,0 +1,144 @@ +use ascii; +use fmt; +use io; +use strings; +use strio; + +const keywords: [_]str = [ + "abort", "align", "alloc", "append", "as", "assert", "bool", "break", + "case", "const", "continue", "defer", "def", "delete", "else", "enum", + "export", "f32", "f64", "false", "fn", "for", "free", "i16", "i32", + "i64", "i8", "if", "insert", "int", "is", "len", "let", "match", "null", + "nullable", "offset", "return", "rune", "size", "static", "str", + "struct", "switch", "true", "type", "u16", "u32", "u64", "u8", "uint", + "uintptr", "union", "use", "vaarg", "vaend", "valist", "vastart", + "void", "yield", +]; + +// Makes an identifier suitable for use in Hare code. +// If the identifier doesn't start with a letter or '_', an '_' is prepended. +// If the identifier matches one of Hare's keywords, an '_' is appended. +fn fix_identifier(s: str) str = { + static let buf: [128]u8 = [0...]; + + let iter = strings::iter(s); + const first = strings::next(&iter) as rune; + if (!ascii::isalpha(first) && first != '_') { + return fmt::bsprintf(buf, "_{}", s); + }; + + for (let i = 0z; i < len(keywords); i += 1) { + if (keywords[i] == s) { + return fmt::bsprintf(buf, "{}_", s); + }; + }; + return s; +}; + +// Normalizes a signal name into a form suitable as an identifier, replacing +// '-' with '_'. +fn normalize_signal(in: str) str = { + static let buf: [128]u8 = [0...]; + let buf = strio::fixed(buf); + + let iter = strings::iter(in); + for (true) match (strings::next(&iter)) { + case let r: rune => + strio::appendrune(&buf, if (r == '-') '_' else r)!; + case => + break; + }; + return strio::string(&buf); +}; + +// Converts lower_case into UPPER_CASE +fn to_uppercase(in: str) str = { + static let buf: [128]u8 = [0...]; + let buf = strio::fixed(buf); + + let iter = strings::iter(in); + for (true) match (strings::next(&iter)) { + case let r: rune => + strio::appendrune(&buf, ascii::toupper(r))!; + case => + break; + }; + return strio::string(&buf); +}; + +// Converts PascalCase into snake_case +fn swap_case(in: str) str = { + let parts: []str = []; + defer strings::freeall(parts); + + let buf = strio::dynamic(); + defer io::close(&buf)!; + + let iter = strings::iter(in); + let prev = '!'; + for (true) match (strings::next(&iter)) { + case let r: rune => + if (ascii::isupper(r) && !ascii::isupper(prev)) { + if (len(strio::string(&buf)) > 0) { + append(parts, + strings::dup(strio::string(&buf))); + strio::reset(&buf); + }; + }; + strio::appendrune(&buf, ascii::tolower(r))!; + prev = r; + case void => + break; + }; + + if (len(strio::string(&buf)) > 0) { + append(parts, strings::dup(strio::string(&buf))); + }; + + return strings::join("_", parts...); +}; + +@test fn swap_case() void = { + const tests = [ + ("Gtk", "gtk"), + ("GtkActionBar", "gtk_action_bar"), + ("GtkWidget", "gtk_widget"), + ("DBus", "dbus"), + ("cairo_t", "cairo_t"), + ]; + for (let i = 0z; i < len(tests); i += 1) { + const (in, expected) = tests[i]; + const out = swap_case(in); + if (out != expected) { + fmt::fatalf("Fail '{}': expected '{}', got '{}'", + in, expected, out); + }; + free(out); + }; +}; + +// A fully quantified identifier. +type ident = []str; + +// Returns whether two fully quantified identifiers match. +fn ident_eq(a: ident, b: ident) bool = { + if (len(a) != len(b)) { + return false; + }; + for (let i = 0z; i < len(a); i += 1) { + if (a[i] != b[i]) { + return false; + }; + }; + return true; +}; + +// Writes a fully quantified identifier to an [[io::handle]]. +fn emit_ident(out: io::handle, id: ident) (void | io::error) = { + for (let i = 0z; i < len(id); i += 1) { + if (i > 0) { + fmt::fprint(out, "::")?; + }; + fmt::fprint(out, id[i])?; + }; +}; diff --git a/cmd/hare-gi/main.ha b/cmd/hare-gi/main.ha new file mode 100644 index 0000000..6aa1213 --- /dev/null +++ b/cmd/hare-gi/main.ha @@ -0,0 +1,148 @@ +use fmt; +use fs; +use getopt; +use gir; +use io; +use os; +use strings; + +export fn main() void = { + const help: [_]getopt::help = [ + "generate Hare bindings from GObject-Introspection files", + ('c', "verify the files instead of generating code"), + ('B', "disable documentation comments"), + ('m', "namespace,<module>,<output", "register a module binding namespace <namespace> to Hare module <module>. If provided, also write the code to <output>"), + "files...", + ]; + const cmd = getopt::parse(os::args, help...); + defer getopt::finish(&cmd); + + let ctx = context { + glib = null: *namespace, + gobject = null: *namespace, + ... + }; + defer context_finish(&ctx); + + let check = false; + for (let i = 0z; i < len(cmd.opts); i += 1) { + const (opt, _) = cmd.opts[i]; + switch (opt) { + case 'c' => + check = true; + case 'B' => + ctx.brief = true; + case 'm' => + yield; // parsed later + case => abort(); + }; + }; + + for (let i = 0z; i < len(cmd.args); i += 1) { + const f = match (os::open(cmd.args[i])) { + case let f: io::file => + yield f; + case let err: fs::error => + fmt::fatalf("Failed to open {}: {}", + cmd.args[i], fs::strerror(err)); + }; + defer io::close(f)!; + + match (gir::parse(f)) { + case let r: gir::repository => + add_repository(&ctx, r); + case let err: gir::error => + fmt::fatalf("Failed to parse {}: {}", + cmd.args[i], gir::strerror(err)); + }; + }; + + for (let i = 0z; i < len(cmd.opts); i += 1) { + const (opt, arg) = cmd.opts[i]; + switch (opt) { + case 'm' => + const (ns, rest) = strings::cut(arg, ","); + const (module, output) = strings::cut(rest, ","); + + if (len(ns) == 0) { + fmt::fatalf("Invalid -o {}: empty namespace", + arg); + }; + if (len(module) == 0) { + fmt::fatalf("Invalid -o {}: empty module", arg); + }; + + const ns = match (get_namespace(&ctx, ns)) { + case let ns: *namespace => + yield ns; + case null => + fmt::fatalf("No such namespace '{}'", ns); + }; + ns.module = strings::split(module, "::"); + if (len(output) > 0) { + match (os::create(output, fs::mode::USER_RW)) { + case let f: io::file => + ns.output_file = f; + case let err: fs::error => + fmt::fatalf("Failed to open {}: {}", + output, fs::strerror(err)); + }; + }; + case => + yield; + }; + }; + + let errors = 0z; + for (let i = 0z; i < len(ctx.namespaces); i += 1) { + const ns = &ctx.namespaces[i]; + if (len(ns.module) == 0) { + fmt::errorln("A module was not provided for namespace", + ns.name)!; + errors += 1; + }; + for (let j = 0z; j < len(ns.includes); j += 1) { + const incl = &ns.includes[j]; + match (get_namespace(&ctx, incl.name, incl.version)) { + case *namespace => yield; + case null => + fmt::errorfln( + "Missing namespace {} (version {}) " + "required by {}", + incl.name, incl.version, ns.name)!; + errors += 1; + }; + }; + }; + if (errors > 0) { + os::exit(1); + }; + + if (check) { + return; + }; + + match (get_namespace(&ctx, "GLib")) { + case let ns: *namespace => + ctx.glib = ns; + case null => + fmt::fatal("GLib namespace is required but was not provided"); + }; + + match (get_namespace(&ctx, "GObject")) { + case let ns: *namespace => + ctx.gobject = ns; + case null => + fmt::fatal("GObject namespace is required but was not provided"); + }; + + populate(&ctx); + + emit_empty(&ctx); + + match (emit(&ctx)) { + case void => yield; + case let err: io::error => + fmt::fatal("Error:", io::strerror(err)); + }; +}; diff --git a/cmd/hare-gi/populate.ha b/cmd/hare-gi/populate.ha new file mode 100644 index 0000000..31e663b --- /dev/null +++ b/cmd/hare-gi/populate.ha @@ -0,0 +1,62 @@ +use gir; +use strings; + +// First stage of codegen: enumerates all types and builds an identifier table, +// which can be then used to get a (namespace, str) pair for a given C +// identifier. See [[lookup_type]]. +fn populate(ctx: *context) void = { + for (let i = 0z; i < len(ctx.namespaces); i += 1) { + populate_namespace(ctx, &ctx.namespaces[i]); + }; +}; + +fn populate_namespace(ctx: *context, ns: *namespace) void = { + ctx.current = ns; + + for (let i = 0z; i < len(ns.aliases); i += 1) { + const alias = &ns.aliases[i]; + populate_type(ctx, alias.c_type, alias.name); + }; + for (let i = 0z; i < len(ns.classes); i += 1) { + const class = &ns.classes[i]; + if (len(class.c_type) > 0) { + populate_type(ctx, class.c_type, class.name); + } else { + populate_type(ctx, class.glib_type_name, class.name); + }; + }; + for (let i = 0z; i < len(ns.interfaces); i += 1) { + const iface = &ns.interfaces[i]; + populate_type(ctx, iface.c_type, iface.name); + }; + for (let i = 0z; i < len(ns.records); i += 1) { + const record = &ns.records[i]; + populate_type(ctx, record.c_type, record.name); + }; + for (let i = 0z; i < len(ns.enums); i += 1) { + const enumeration = &ns.enums[i]; + populate_type(ctx, enumeration.c_type, enumeration.name); + }; + for (let i = 0z; i < len(ns.unions); i += 1) { + const union_ = &ns.unions[i]; + populate_type(ctx, union_.c_type, union_.name); + }; + for (let i = 0z; i < len(ns.bitfields); i += 1) { + const bitfield = &ns.bitfields[i]; + populate_type(ctx, bitfield.c_type, bitfield.name); + }; + for (let i = 0z; i < len(ns.callbacks); i += 1) { + const callback = &ns.callbacks[i]; + if (len(callback.c_type) > 0) { + populate_type(ctx, callback.c_type, callback.name); + }; + }; +}; + +fn populate_type(ctx: *context, c: str, name: str) void = { + if (len(c) == 0) { + return; + }; + const hare = strings::dup(fix_identifier(name)); + append(ctx.current.types, (c, hare)); +}; diff --git a/cmd/xmltree/main.ha b/cmd/xmltree/main.ha new file mode 100644 index 0000000..5f15eaa --- /dev/null +++ b/cmd/xmltree/main.ha @@ -0,0 +1,104 @@ +use xml = format::fastxml; +use sort; +use os; +use fmt; +use strings; + +type node = struct { + name: str, + attributes: []str, + has_text: bool, + + children: []*node, +}; + +fn add_attr(n: *node, attr: xml::attribute) void = { + for (let i = 0z; i < len(n.attributes); i += 1) { + if (n.attributes[i] == attr.0) return; + }; + append(n.attributes, strings::dup(attr.0)); +}; + +fn add_text(n: *node, text: str) void = { + const clean = strings::trim(text); + n.has_text ||= len(clean) > 0; +}; + +fn get_node(n: *node, name: str) *node = { + for (let i = 0z; i < len(n.children); i += 1) { + if (n.children[i].name == name) + return n.children[i]; + }; + let new = alloc(node { name = strings::dup(name), ... }); + append(n.children, new); + return new; +}; + +fn print(n: *node, indent: size) void = { + for (let i = 0z; i < indent; i += 1) fmt::print("> ")!; + + fmt::print(n.name)!; + if (n.has_text) fmt::print("+")!; + fmt::print(" [")!; + for (let i = 0z; i < len(n.attributes); i += 1) { + fmt::printf("{}{}", if (i == 0) "" else " ", n.attributes[i])!; + }; + fmt::println("]")!; + + for (let i = 0z; i < len(n.children); i += 1) { + print(n.children[i], indent + 1); + }; +}; + +export fn main() void = { + if (len(os::args) - 1 < 1) { + fmt::fatal("Usage: xmltree <file.xml>"); + }; + + const file = os::open(os::args[1])!; + const parser = xml::parse(file)!; + defer xml::parser_free(parser); + + let tree = null: *node; + defer free(tree); // memory leaks! + + let stack: []*node = []; + defer free(stack); + + for (true) { + const token = match (xml::scan(parser)!) { + case let t: xml::token => + yield t; + case void => + break; + }; + + match (token) { + case let start: xml::elementstart => + if (len(stack) == 0) { + tree = alloc(node { + name = strings::dup(start), + ... + }); + append(stack, tree); + } else { + let current = stack[len(stack) - 1]; + append(stack, get_node(current, start)); + }; + case let end: xml::elementend => + //fmt::printfln("stack => {}", stack[len(stack) - 1].name)!; + //fmt::printfln("file => {}", end)!; + assert(end == stack[len(stack) - 1].name); + delete(stack[len(stack) - 1]); + if (len(stack) == 0) break; + case let attr: xml::attribute => + let current = stack[len(stack) - 1]; + add_attr(current, attr); + case let text: xml::text => + let current = stack[len(stack) - 1]; + add_text(current, text); + }; + }; + + print(tree, 0); +}; |