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 |
Initial commit
-rw-r--r-- | .gitignore | 6 | ||||
-rw-r--r-- | COPYING | 367 | ||||
-rw-r--r-- | Makefile | 55 | ||||
-rw-r--r-- | README.md | 59 | ||||
-rw-r--r-- | bindings/glib/types.ha | 8 | ||||
-rw-r--r-- | bindings/gobject/types.ha | 13 | ||||
-rw-r--r-- | bindings/types/libc/types.ha | 27 | ||||
-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 | ||||
-rwxr-xr-x | generate | 40 | ||||
-rwxr-xr-x | generate4 | 47 | ||||
-rw-r--r-- | gir/parse.ha | 742 | ||||
-rw-r--r-- | gir/types.ha | 363 |
20 files changed, 3398 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4d33af7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/xmltree +/hare-gi +/demo +/demo4 + +_generated.ha diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..c257317 --- /dev/null +++ b/COPYING @@ -0,0 +1,367 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2b41877 --- /dev/null +++ b/Makefile @@ -0,0 +1,55 @@ +.POSIX: +.SUFFIXES: + +BINDINGS_DIR = bindings +CORE_MODULES = glib gobject gio gmodule atk gdkpixbuf freetype harfbuzz cairo pango xlib types/libc +MODULES3 = $(CORE_MODULES) gdk gtk +MODULES4 = $(CORE_MODULES) gdk4 gtk4 pangocairo graphene gsk + +DESTDIR = +PREFIX = /usr/local +SRCDIR = $(PREFIX)/src +HARESRCDIR = $(SRCDIR)/hare +THIRDPARTYDIR = $(HARESRCDIR)/third-party + +all: + @true + +install: install-core + @for mod in $(MODULES3); do \ + printf "installing %s\n" "$$mod"; \ + mkdir -p "$(DESTDIR)$(THIRDPARTYDIR)/$$mod"; \ + install -m644 "$(BINDINGS_DIR)/$$mod"/* "$(DESTDIR)$(THIRDPARTYDIR)/$$mod"; \ + done + +install4: install-core + @for mod in $(MODULES4); do \ + printf "installing %s\n" "$$mod"; \ + mkdir -p "$(DESTDIR)$(THIRDPARTYDIR)/$$mod"; \ + install -m644 "$(BINDINGS_DIR)/$$mod"/* "$(DESTDIR)$(THIRDPARTYDIR)/$$mod"; \ + done + +uninstall: + @for mod in $(MODULES3); do \ + printf "uninstalling %s\n" "$$mod"; \ + rm -r "$(DESTDIR)$(THIRDPARTYDIR)/$$mod"; \ + done + +uninstall4: + @for mod in $(MODULES4); do \ + printf "uninstalling %s\n" "$$mod"; \ + rm -r "$(DESTDIR)$(THIRDPARTYDIR)/$$mod"; \ + done + +demo: + env HAREPATH="$(BINDINGS_DIR):$$(hare version -v | grep HAREPATH | cut -f2-)" \ + hare build $$(pkg-config --libs-only-l --static gtk+-3.0) -lbsd -o demo cmd/demo + +demo4: + env HAREPATH="$(BINDINGS_DIR):$$(hare version -v | grep HAREPATH | cut -f2-)" \ + hare build $$(pkg-config --libs-only-l --static gtk4) -lepoxy -lXi -lxkbcommon -lwayland-client -lwayland-egl -lXfixes -lXcursor -lXdamage -lXrandr -lXinerama -lcairo-script-interpreter -lbsd -o demo4 cmd/demo4 + +clean: + rm -f hare-gi xmltree demo demo4 + +.PHONY: all install install4 uninstall uninstall4 demo demo4 clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..559c6cf --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# hare-gi +GObject Introspection code generator for Hare. In short, it creates bindings for +GTK family of libraries (GTK+, GDK, Pango, etc). + +## Generating and installing bindings +Dependencies: +* An up-to-date [Hare] toolchain +* [hare-fastxml] +* Development files for the desired GTK version and its dependencies. Make sure +`/usr/share/gir-1.0` is included. + +A few core libraries do not ship GIR XML files by default. They can be obtained +from gobject-introspection: + +``` +git clone https://gitlab.gnome.org/GNOME/gobject-introspection.git +meson setup build/ +ninja -C build/ + +# The path where this is downloaded will be used later +``` + +### GTK 3 +``` +./generate <path to gobject-introspection> +make install +``` + +### GTK 4 +``` +./generate4 <path to gobject-introspection> +make install4 +``` + +## Running examples +### GTK 3 +``` +make demo +./demo +``` + +### GTK 4 +``` +make demo4 +./demo4 +``` + +## Contributing +Please send patches to [~yerinalexey/public-inbox@lists.sr.ht][archives] using +[git send-email] with prefix set to `hare-gi`: + +```shell-session +$ git config format.subjectPrefix "PATCH hare-gi" +``` + +[Hare]: https://harelang.org/installation +[hare-fastxml]: https://git.sr.ht/~yerinalexey/hare-fastxml +[archives]: https://lists.sr.ht/~yerinalexey/public-inbox +[git send-email]: https://git-send-email.io diff --git a/bindings/glib/types.ha b/bindings/glib/types.ha new file mode 100644 index 0000000..c973570 --- /dev/null +++ b/bindings/glib/types.ha @@ -0,0 +1,8 @@ +// Boolean type used by GLib and related libraries, equivalent to gboolean in C. +export type boolean = uint; + +// True value of [[boolean]]. +export def TRUE: boolean = 1; + +// False value of [[boolean]]. +export def FALSE: boolean = 0; diff --git a/bindings/gobject/types.ha b/bindings/gobject/types.ha new file mode 100644 index 0000000..193bffd --- /dev/null +++ b/bindings/gobject/types.ha @@ -0,0 +1,13 @@ +use types::c; + +export type ValueUnion = union { + v_int: int, + v_uint: uint, + v_long: c::long, + v_ulong: c::ulong, + v_int64: i64, + v_uint64: u64, + v_float: f32, + v_double: f64, + v_pointer: *void, +}; diff --git a/bindings/types/libc/types.ha b/bindings/types/libc/types.ha new file mode 100644 index 0000000..bfb0dc0 --- /dev/null +++ b/bindings/types/libc/types.ha @@ -0,0 +1,27 @@ +use types::c; + +export type time_t = i64; + +export type tm = struct { + tm_sec: int, + tm_min: int, + tm_hour: int, + tm_mday: int, + tm_mon: int, + tm_year: int, + tm_wday: int, + tm_yday: int, + tm_isdst: int, +}; + +export type FILE = void; + +export type passwd = struct { + pw_name: *c::char, + pw_passwd: *c::char, + pw_uid: uint, + pw_gid: uint, + pw_gecos: *c::char, + pw_dir: *c::char, + pw_shell: *c::char, +}; 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); +}; diff --git a/generate b/generate new file mode 100755 index 0000000..7f62ada --- /dev/null +++ b/generate @@ -0,0 +1,40 @@ +#!/bin/sh + +if [ $# -lt 1 ]; then + printf "Usage: generate <gobject introspection root>\n" >&2 + exit 1 +fi +GI="$1" +BIND=./bindings + +mkdir -p "$BIND"/glib "$BIND"/gobject "$BIND"/gio "$BIND"/gmodule "$BIND"/atk \ + "$BIND"/gdkpixbuf "$BIND"/freetype "$BIND"/harfbuzz "$BIND"/cairo \ + "$BIND"/pango "$BIND"/gdk "$BIND"/xlib "$BIND"/gtk + +hare build cmd/hare-gi && ./hare-gi -B \ + -m GLib,glib,"$BIND"/glib/_generated.ha \ + -m GObject,gobject,"$BIND"/gobject/_generated.ha \ + -m Gio,gio,"$BIND"/gio/_generated.ha \ + -m GModule,gmodule,"$BIND"/gmodule/_generated.ha \ + -m Atk,atk,"$BIND"/atk/_generated.ha \ + -m GdkPixbuf,gdkpixbuf,"$BIND"/gdkpixbuf/_generated.ha \ + -m freetype2,freetype,"$BIND"/freetype/_generated.ha \ + -m HarfBuzz,harfbuzz,"$BIND"/harfbuzz/_generated.ha \ + -m cairo,cairo,"$BIND"/cairo/_generated.ha \ + -m Pango,pango,"$BIND"/pango/_generated.ha \ + -m Gdk,gdk,"$BIND"/gdk/_generated.ha \ + -m xlib,xlib,"$BIND"/xlib/_generated.ha \ + -m Gtk,gtk,"$BIND"/gtk/_generated.ha \ + "$GI"/build/gir/GLib-2.0.gir \ + "$GI"/build/gir/GObject-2.0.gir \ + "$GI"/build/gir/Gio-2.0.gir \ + "$GI"/build/gir/GModule-2.0.gir \ + /usr/share/gir-1.0/Atk-1.0.gir \ + /usr/share/gir-1.0/GdkPixbuf-2.0.gir \ + "$GI"/build/gir/freetype2-2.0.gir \ + /usr/share/gir-1.0/HarfBuzz-0.0.gir \ + "$GI"/build/gir/cairo-1.0.gir \ + /usr/share/gir-1.0/Pango-1.0.gir \ + /usr/share/gir-1.0/Gdk-3.0.gir \ + "$GI"/build/gir/xlib-2.0.gir \ + /usr/share/gir-1.0/Gtk-3.0.gir diff --git a/generate4 b/generate4 new file mode 100755 index 0000000..8fcff42 --- /dev/null +++ b/generate4 @@ -0,0 +1,47 @@ +#!/bin/sh + +if [ $# -lt 1 ]; then + printf "Usage: generate4 <gobject introspection root>\n" >&2 + exit 1 +fi +GI="$1" +BIND=./bindings + +mkdir -p "$BIND"/glib "$BIND"/gobject "$BIND"/gio "$BIND"/gmodule "$BIND"/atk \ + "$BIND"/gdkpixbuf "$BIND"/freetype "$BIND"/harfbuzz "$BIND"/cairo \ + "$BIND"/pango "$BIND"/pangocairo "$BIND"/gdk4 "$BIND"/xlib "$BIND"/graphene \ + "$BIND"/gsk "$BIND"/gtk4 + +hare build cmd/hare-gi && ./hare-gi -B \ + -m GLib,glib,"$BIND"/glib/_generated.ha \ + -m GObject,gobject,"$BIND"/gobject/_generated.ha \ + -m Gio,gio,"$BIND"/gio/_generated.ha \ + -m GModule,gmodule,"$BIND"/gmodule/_generated.ha \ + -m Atk,atk,"$BIND"/atk/_generated.ha \ + -m GdkPixbuf,gdkpixbuf,"$BIND"/gdkpixbuf/_generated.ha \ + -m freetype2,freetype,"$BIND"/freetype/_generated.ha \ + -m HarfBuzz,harfbuzz,"$BIND"/harfbuzz/_generated.ha \ + -m cairo,cairo,"$BIND"/cairo/_generated.ha \ + -m Pango,pango,"$BIND"/pango/_generated.ha \ + -m PangoCairo,pangocairo,"$BIND"/pangocairo/_generated.ha \ + -m Gdk,gdk4,"$BIND"/gdk4/_generated.ha \ + -m xlib,xlib,"$BIND"/xlib/_generated.ha \ + -m Graphene,graphene,"$BIND"/graphene/_generated.ha \ + -m Gsk,gsk,"$BIND"/gsk/_generated.ha \ + -m Gtk,gtk4,"$BIND"/gtk4/_generated.ha \ + "$GI"/build/gir/GLib-2.0.gir \ + "$GI"/build/gir/GObject-2.0.gir \ + "$GI"/build/gir/Gio-2.0.gir \ + "$GI"/build/gir/GModule-2.0.gir \ + /usr/share/gir-1.0/Atk-1.0.gir \ + /usr/share/gir-1.0/GdkPixbuf-2.0.gir \ + "$GI"/build/gir/freetype2-2.0.gir \ + /usr/share/gir-1.0/HarfBuzz-0.0.gir \ + "$GI"/build/gir/cairo-1.0.gir \ + /usr/share/gir-1.0/Pango-1.0.gir \ + /usr/share/gir-1.0/PangoCairo-1.0.gir \ + /usr/share/gir-1.0/Gdk-4.0.gir \ + "$GI"/build/gir/xlib-2.0.gir \ + /usr/share/gir-1.0/Graphene-1.0.gir \ + /usr/share/gir-1.0/Gsk-4.0.gir \ + /usr/share/gir-1.0/Gtk-4.0.gir diff --git a/gir/parse.ha b/gir/parse.ha new file mode 100644 index 0000000..f844cd9 --- /dev/null +++ b/gir/parse.ha @@ -0,0 +1,742 @@ +use fmt; +use io; +use strconv; +use strings; +use xml = format::fastxml; + +// The provided GIR file is invalid. +export type invalid = !void; + +// Any error which can occur during GIR parsing. +export type error = !(invalid | xml::error); + +// Returns a human-friendly representation of an [[error]]. +export fn strerror(err: error) const str = { + match (err) { + case invalid => + return "File does not follow GIR structure"; + case let err: xml::error => + return xml::strerror(err); + }; +}; + +// Parses a file into a [[repository]]. +export fn parse(in: io::file) (repository | error) = { + const parser = xml::parse(in)?; + defer xml::parser_free(parser); + + let repo = repository { ... }; + parse_element(parser, "", + ("repository", &parse_repository, &repo), + )?; + return repo; +}; + +fn parse_repository(parser: *xml::parser, repo: *void) (void | error) = { + const repo = repo: *repository; + return parse_element(parser, "repository", + ("xmlns", null), + ("xmlns:c", null), + ("xmlns:glib", null), + ("version", &repo.version), + ("c:identifier-prefixes", null), + ("c:symbol-prefixes", null), + ("include", &parse_include, &repo.includes), + ("c:include", null, null), + ("package", null, null), + ("namespace", &parse_namespace, &repo.namespaces), + ); +}; + +fn parse_include(parser: *xml::parser, includes: *void) (void | error) = { + const includes = includes: *[]include; + let new = include { ... }; + parse_element(parser, "include", + ("name", &new.name), + ("version", &new.version), + )?; + append(includes, new); +}; + +fn parse_namespace(parser: *xml::parser, namespaces: *void) (void | error) = { + const namespaces = namespaces: *[]namespace; + let new = namespace { ... }; + parse_element(parser, "namespace", + ("name", &new.name), + ("version", &new.version), + ("c:identifier-prefixes", null), + ("c:symbol-prefixes", null), + ("shared-library", null), + + ("alias", &parse_alias, &new.aliases), + ("class", &parse_class, &new.classes), + ("interface", &parse_interface, &new.interfaces), + ("record", &parse_record, &new.records), + ("enumeration", &parse_enumeration, &new.enums), + ("function", &parse_function, &new.functions), + ("union", &parse_union, &new.unions), + ("bitfield", &parse_bitfield, &new.bitfields), + ("callback", &parse_callback, &new.callbacks), + ("constant", &parse_constant, &new.constants), + ("annotation", null, null), + ("glib:boxed", null, null), + + // Undocumented and unsupported + ("docsection", null, null), + ("function-macro", null, null), + )?; + append(namespaces, new); +}; + +fn parse_alias(parser: *xml::parser, aliases: *void) (void | error) = { + const aliases = aliases: *[]alias; + let new = alias { ... }; + parse_element(parser, "alias", + parse_info(&new), + ("name", &new.name), + ("c:type", &new.c_type), + ("type", &parse_simple_type, &new.inner), + )?; + append(aliases, new); +}; + +fn parse_class(parser: *xml::parser, classes: *void) (void | error) = { + const classes = classes: *[]class; + let new = class { ... }; + parse_element(parser, "class", + parse_info(&new), + ("name", &new.name), + ("glib:type-name", &new.glib_type_name), + ("glib:get-type", &new.glib_get_type), + ("parent", &new.parent), + ("glib:type-struct", null), + ("glib:ref-func", &new.glib_ref_func), + ("glib:unref-func", &new.glib_unref_func), + ("glib:set-value-func", &new.glib_set_value_func), + ("glib:get-value-func", &new.glib_get_value_func), + ("c:type", &new.c_type), + ("c:symbol-prefix", &new.symbol_prefix), + ("abstract", &new.abstract), + ("glib:fundamental", &new.fundamental), + ("final", &new.final), + + ("implements", &parse_implements, &new.implements), + ("constructor", &parse_constructor, &new.constructors), + ("method", &parse_method, &new.methods), + ("function", &parse_function, &new.functions), + ("virtual-method", &parse_virtual_method, &new.virtual_methods), + ("field", &parse_field, &new.entries), + ("property", &parse_property, &new.properties), + ("glib:signal", &parse_signal, &new.signals), + ("union", &parse_union_entry, &new.entries), + ("constant", &parse_constant, &new.constants), + ("record", &parse_record_entry, &new.entries), + ("callback", &parse_callback, &new.callbacks), + )?; + append(classes, new); +}; +fn parse_implements(parser: *xml::parser, implements: *void) (void | error) = { + const implements = implements: *[]str; + let name = ""; + parse_element(parser, "implements", + ("name", &name), + )?; + append(implements, name); +}; +fn parse_interface(parser: *xml::parser, interfaces: *void) (void | error) = { + const interfaces = interfaces: *[]interface; + let new = interface { ... }; + parse_element(parser, "interface", + parse_info(&new), + ("name", &new.name), + ("glib:type-name", &new.glib_type_name), + ("glib:get-type", &new.glib_get_type), + ("c:symbol-prefix", null), + ("c:type", &new.c_type), + ("glib:type-struct", null), + + ("prerequisite", &parse_prerequisite, &new.prerequisites), + ("implements", &parse_implements, &new.implements), + ("function", &parse_function, &new.functions), + ("constructor", &parse_constructor, &new.constructors), + ("method", &parse_method, &new.methods), + ("virtual-method", &parse_virtual_method, &new.virtual_methods), + ("field", &parse_field, &new.entries), + ("property", &parse_property, &new.properties), + ("glib:signal", &parse_signal, &new.signals), + ("callback", &parse_callback, &new.callbacks), + ("constant", &parse_constant, &new.constants), + )?; + append(interfaces, new); +}; +fn parse_prerequisite(parser: *xml::parser, prerequisites: *void) (void | error) = { + const prerequisites = prerequisites: *[]str; + let name = ""; + parse_element(parser, "prerequisite", + ("name", &name), + )?; + append(prerequisites, name); +}; + +fn parse_record(parser: *xml::parser, records: *void) (void | error) = { + const records = records: *[]record; + let new = record { ... }; + parse_element(parser, "record", + parse_info(&new), + ("name", &new.name), + ("c:type", &new.c_type), + ("disguised", &new.disguised), + ("opaque", &new.opaque), + ("pointer", &new.pointer), + ("glib:type-name", &new.glib_type_name), + ("glib:get-type", &new.glib_get_type), + ("c:symbol-prefix", null), + ("foreign", &new.foreign), + ("glib:is-gtype-struct-for", null), + ("copy-function", &new.copy_function), + ("free-function", &new.free_function), + + ("field", &parse_field, &new.entries), + ("function", &parse_function, &new.functions), + ("union", &parse_union_entry, &new.entries), + ("method", &parse_method, &new.methods), + ("constructor", &parse_constructor, &new.constructors), + )?; + append(records, new); +}; + +fn parse_record_entry(parser: *xml::parser, entries: *void) (void | error) = { + const entries = entries: *[]entry; + let records: []record = []; + defer free(records); + parse_record(parser, &records)?; + append(entries, records[0]); +}; + +fn parse_constructor(parser: *xml::parser, constructors: *void) (void | error) = { + const constructors = constructors: *[]constructor; + let new = constructor { ... }; + parse_element(parser, "constructor", + parse_callable(&new), + ("parameters", &parse_parameters, &new.params), + ("return-value", &parse_return_value, &new.return_value), + )?; + append(constructors, new); +}; +fn parse_method(parser: *xml::parser, methods: *void) (void | error) = { + const methods = methods: *[]method; + let new = method { ... }; + parse_element(parser, "method", + parse_callable(&new), + ("glib:set-property", &new.glib_set_property), + ("glib:get-property", &new.glib_get_property), + ("parameters", &parse_method_parameters, + &(&new.instance, &new.params)), + ("return-value", &parse_return_value, &new.return_value), + )?; + append(methods, new); +}; +fn parse_virtual_method(parser: *xml::parser, vmethods: *void) (void | error) = { + const vmethods = vmethods: *[]virtual_method; + let new = virtual_method { ... }; + parse_element(parser, "virtual-method", + parse_callable(&new), + ("invoker", &new.invoker), + ("parameters", &parse_method_parameters, + &(&new.instance, &new.params)), + ("return-value", &parse_return_value, &new.return_value), + )?; + append(vmethods, new); +}; +fn parse_field(parser: *xml::parser, entries: *void) (void | error) = { + const entries = entries: *[]entry; + let new = field { ... }; + let type_: any_type = simple_type { ... }; + let cb = callback { ... }; + parse_element(parser, "field", + parse_info(&new), + ("name", &new.name), + ("writable", &new.writable), + ("readable", &new.readable), + ("private", &new.private), + ("bits", &new.bits), + + ("callback", &parse_one_callback, &cb), + parse_any_type(&type_), + )?; + if (len(cb.name) > 0) { + new.type_ = cb; + } else { + new.type_ = type_; + }; + append(entries, new); +}; +fn parse_property(parser: *xml::parser, properties: *void) (void | error) = { + const properties = properties: *[]property; + let new = property { ... }; + parse_element(parser, "property", + parse_info(&new), + ("name", &new.name), + ("writable", &new.writable), + ("readable", &new.readable), + ("construct", &new.construct), + ("construct-only", &new.construct_only), + ("setter", &new.setter), + ("getter", &new.getter), + ("default-value", &new.default_value), + ("transfer-ownership", &new.transfer_ownership), + + parse_any_type(&new.type_), + )?; + append(properties, new); +}; + +fn parse_enumeration(parser: *xml::parser, enums: *void) (void | error) = { + const enums = enums: *[]enumeration; + let new = enumeration { ... }; + parse_element(parser, "enumeration", + parse_info(&new), + ("name", &new.name), + ("c:type", &new.c_type), + ("glib:type-name", &new.glib_type_name), + ("glib:get-type", &new.glib_get_type), + ("glib:error-domain", &new.glib_error_domain), + + ("member", &parse_member, &new.members), + ("function", &parse_function, &new.functions), + )?; + append(enums, new); +}; + +fn parse_function(parser: *xml::parser, functions: *void) (void | error) = { + const functions = functions: *[]function; + let new = function { ... }; + parse_element(parser, "function", + parse_callable(&new), + ("parameters", &parse_parameters, &new.params), + ("return-value", &parse_return_value, &new.return_value), + )?; + append(functions, new); +}; + +fn parse_union(parser: *xml::parser, unions: *void) (void | error) = { + const unions = unions: *[]union_; + let new = union_ { ... }; + parse_element(parser, "union", + parse_info(&new), + ("name", &new.name), + ("c:type", &new.c_type), + ("c:symbol-prefix", null), + ("glib:type-name", &new.glib_type_name), + ("glib:get-type", &new.glib_get_type), + ("copy-function", &new.copy_function), + ("free-function", &new.free_function), + + ("field", &parse_field, &new.entries), + ("constructor", &parse_constructor, &new.constructors), + ("method", &parse_method, &new.methods), + ("function", &parse_function, &new.functions), + ("record", &parse_record_entry, &new.entries), + )?; + append(unions, new); +}; + +fn parse_union_entry(parser: *xml::parser, entries: *void) (void | error) = { + const entries = entries: *[]entry; + let unions: []union_ = []; + defer free(unions); + parse_union(parser, &unions)?; + append(entries, unions[0]); +}; + +fn parse_bitfield(parser: *xml::parser, bitfields: *void) (void | error) = { + const bitfields = bitfields: *[]bitfield; + let new = bitfield { ... }; + parse_element(parser, "bitfield", + parse_info(&new), + ("name", &new.name), + ("c:type", &new.c_type), + ("glib:type-name", &new.glib_type_name), + ("glib:get-type", &new.glib_get_type), + + ("member", &parse_member, &new.members), + ("function", &parse_function, &new.functions), + )?; + append(bitfields, new); +}; + +fn parse_callback(parser: *xml::parser, callbacks: *void) (void | error) = { + const callbacks = callbacks: *[]callback; + let new = callback { ... }; + parse_one_callback(parser, &new)?; + append(callbacks, new); +}; +fn parse_one_callback(parser: *xml::parser, cb: *void) (void | error) = { + const cb = cb: *callback; + return parse_element(parser, "callback", + parse_info(cb), + ("name", &cb.name), + ("c:type", &cb.c_type), + ("throws", &cb.throws), + + ("parameters", &parse_parameters, &cb.params), + ("return-value", &parse_return_value, &cb.return_value), + ); +}; + +fn parse_constant(parser: *xml::parser, constants: *void) (void | error) = { + const constants = constants: *[]constant; + let new = constant { ... }; + let type_: any_type = simple_type { ... }; + parse_element(parser, "constant", + parse_info(&new), + ("name", &new.name), + ("value", &new.value), + ("c:type", &new.c_type), + ("c:identifier", &new.c_identifier), + parse_any_type(&type_), + )?; + if (type_ is array_type || len((type_ as simple_type).name) > 0) { + new.type_ = type_; + } else { + new.type_ = void; + }; + append(constants, new); +}; + +fn parse_signal(parser: *xml::parser, signals: *void) (void | error) = { + const signals = signals: *[]signal; + let new = signal { ... }; + parse_element(parser, "glib:signal", + parse_info(&new), + ("name", &new.name), + ("detailed", &new.detailed), + ("when", &new.when), + ("action", &new.action), + ("no-hooks", &new.no_hooks), + ("no-recurse", &new.no_recurse), + ("emitter", &new.emitter), + + ("parameters", &parse_parameters, &new.params), + ("return-value", &parse_return_value, &new.return_value), + )?; + append(signals, new); +}; + +// enum + bitfield +fn parse_member(parser: *xml::parser, members: *void) (void | error) = { + const members = members: *[]member; + let new = member { ... }; + parse_element(parser, "member", + parse_info(&new), + ("name", &new.name), + ("value", &new.value), + ("c:identifier", &new.c_identifier), + ("glib:nick", &new.glib_nick), + ("glib:name", &new.glib_name), + )?; + append(members, new); +}; + +// callable +fn parse_callable(callable: *callable) [17]scan = [ + ("introspectable", null), + ("deprecated", null), + ("deprecated-version", null), + ("version", null), + ("stability", null), + ("doc", &parse_doc, &callable.doc), + ("doc-version", null, null), + ("doc-stability", null, null), + ("doc-deprecated", null, null), + ("source-position", null, null), + ("attribute", null, null), + ("name", &callable.name), + ("c:identifier", &callable.c_identifier), + ("shadowed-by", &callable.shadowed_by), + ("shadows", &callable.shadows), + ("throws", &callable.throws), + ("moved-to", null), +]; +fn parse_parameters(parser: *xml::parser, parameters: *void) (void | error) = { + return parse_element(parser, "parameters", + ("parameter", &parse_parameter, parameters), + ("varargs", &parse_varargs, parameters), + ); +}; +fn parse_method_parameters(parser: *xml::parser, parameters: *void) (void | error) = { + const parameters = parameters: *(*instance_parameter, *[]callable_param); + return parse_element(parser, "parameters", + ("instance-parameter", &parse_instance_parameter, parameters.0), + ("parameter", &parse_parameter, parameters.1), + ("varargs", &parse_varargs, parameters.1), + ); +}; +fn parse_instance_parameter(parser: *xml::parser, param: *void) (void | error) = { + const param = param: *instance_parameter; + return parse_element(parser, "instance-parameter", + parse_info(param), + ("name", ¶m.name), + ("nullable", ¶m.is_nullable), + ("allow-none", ¶m.allow_none), + ("direction", ¶m.direction), + ("caller-allocates", ¶m.caller_allocates), + ("transfer-ownership", ¶m.transfer_ownership), + + ("type", &parse_simple_type, ¶m.type_), + ); +}; +fn parse_parameter(parser: *xml::parser, parameters: *void) (void | error) = { + const parameters = parameters: *[]callable_param; + let new = callable_param { ... }; + let type_: any_type = simple_type { ... }; + let has_varargs = false; + parse_element(parser, "parameter", + parse_info(&new), + ("name", &new.name), + ("nullable", &new.is_nullable), + ("allow-none", &new.allow_none), + ("introspectable", &new.introspectable), + ("closure", &new.closure), + ("destroy", &new.destroy), + ("scope", &new.scope), + ("direction", &new.direction), + ("caller-allocates", &new.caller_allocates), + ("optional", &new.optional), + ("skip", &new.skip), + ("transfer-ownership", &new.transfer_ownership), + ("varargs", &parse_varargs, &has_varargs), + parse_any_type(&type_), + )?; + if (has_varargs) { + new.parameter = varargs; + } else { + new.parameter = type_; + }; + append(parameters, new); +}; +fn parse_varargs(parser: *xml::parser, has_varargs: *void) (void | error) = { + *(has_varargs: *bool) = true; + return parse_element(parser, "varargs"); +}; +fn parse_return_value(parser: *xml::parser, return_value: *void) (void | error) = { + const return_value = return_value: *(callable_return | void); + let new = callable_return { ... }; + parse_element(parser, "return-value", + parse_info(&new), + ("introspectable", &new.introspectable), + ("nullable", &new.is_nullable), + ("closure", &new.closure), + ("destroy", &new.destroy), + ("skip", &new.skip), + ("allow-none", &new.allow_none), + ("transfer-ownership", &new.transfer_ownership), + parse_any_type(&new.type_), + )?; + *return_value = new; +}; + +// documentation +fn parse_info(doc: *documentation) [11]scan = [ + ("introspectable", null), + ("deprecated", null), + ("deprecated-version", null), + ("version", null), + ("stability", null), + ("doc", &parse_doc, &doc.doc), + ("doc-version", null, null), + ("doc-stability", null, null), + ("doc-deprecated", null, null), + ("source-position", null, null), + ("attribute", null, null), +]; +fn parse_doc(parser: *xml::parser, text: *void) (void | error) = { + const text = text: *str; + return parse_element(parser, "doc", + ("xml:space", null), + ("xml:whitespace", null), + ("filename", null), + ("line", null), + ("column", null), + text, + ); +}; + +// types +fn parse_simple_type(parser: *xml::parser, out: *void) (void | error) = { + const out = out: *simple_type; + return parse_element(parser, "type", + parse_info(out), + ("name", &out.name), + ("c:type", &out.c_type), + ("introspectable", &out.introspectable), + ("type", null, null), + ("array", null, null), + ); +}; + +fn parse_array_type(parser: *xml::parser, out: *void) (void | error) = { + const out = out: *array_type; + let inner: any_type = simple_type { ... }; + parse_element(parser, "array", + parse_info(out), + ("name", &out.name), + ("zero-terminated", &out.zero_terminated), + ("fixed-size", &out.fixed_size), + ("introspectable", &out.introspectable), + ("length", &out.length), + ("c:type", &out.c_type), + parse_any_type(&inner), + )?; + out.inner = alloc(inner); +}; + +fn parse_any_type(out: *any_type) [2]scan = [ + ("type", &parse_simple_type_union, out), + ("array", &parse_array_type_union, out), +]; +fn parse_simple_type_union(parser: *xml::parser, out: *void) (void | error) = { + const out = out: *any_type; + let simple = simple_type { ... }; + parse_simple_type(parser, &simple)?; + *out = simple; +}; +fn parse_array_type_union(parser: *xml::parser, out: *void) (void | error) = { + const out = out: *any_type; + let array = array_type { ... }; + parse_array_type(parser, &array)?; + *out = array; +}; + +type parse_func = fn(parser: *xml::parser, data: *void) (void | error); +type scan_element = ( + str, + nullable *parse_func, + nullable *void, +); +type scan_attribute = ( + str, + (nullable *str | *uint | *bool), +); +type scan_text = *str; +type scan = (scan_element | scan_attribute | scan_text | []scan); + +fn parse_element( + parser: *xml::parser, + current: str, + scans: scan... +) (void | error) = { + let elements: []scan_element = []; + defer free(elements); + let attrs: []scan_attribute = []; + defer free(attrs); + let text_out: nullable *str = null; + + for (let i = 0z; i < len(scans); i += 1) { + match (scans[i]) { + case let s: []scan => + for (let j = 0z; j < len(s); j += 1) match (s[j]) { + case let s: scan_element => + append(elements, s); + case let s: scan_attribute => + append(attrs, s); + case let s: scan_text => + assert(text_out is null, "only one scan_text is allowed"); + text_out = s; + case => abort("too deep"); + }; + case let s: scan_element => + append(elements, s); + case let s: scan_attribute => + append(attrs, s); + case let s: scan_text => + assert(text_out is null, "only one scan_text is allowed"); + text_out = s; + }; + }; + + for (true) match (xml::scan(parser)?) { + case let start: xml::elementstart => + let found = false; + for (let i = 0z; i < len(elements); i += 1) { + const (name, func, data) = elements[i]; + if (start == name) { + match (func) { + case let func: *parse_func => + func(parser, data: *void)?; + case null => + ignore(parser, name)?; + }; + found = true; + break; + }; + }; + if (!found) { + fmt::fatalf("Unhandled element {}->{}", current, start); + //return invalid; + }; + case let end: xml::elementend => + assert(len(end) == 0 || end == current); + break; + case let attr: xml::attribute => + let found = false; + for (let i = 0z; i < len(attrs); i += 1) { + const (name, out) = attrs[i]; + if (attr.0 == name) { + match (out) { + case let s: nullable *str => + match(s) { + case let s: *str => + *s = strings::dup(attr.1); + case null => yield; + }; + case let b: *bool => + *b = (attr.1 == "1"); + case let u: *uint => + match (strconv::stou(attr.1)) { + case let parsed: uint => + *u = parsed; + case => + return invalid; + }; + }; + found = true; + break; + }; + }; + if (!found) { + fmt::fatalf("Unhandled attribute {}->{}", current, attr.0); + //return invalid; + }; + case let text: xml::text => + if (len(strings::trim(text)) > 0) { + match (text_out) { + case let s: *str => + *s = strings::dup(text); + case null => + yield; + }; + }; + case void => + if (len(current) == 0) { + break; + }; + return invalid; + }; +}; + +fn ignore(parser: *xml::parser, name: str) (void | error) = { + const name = strings::dup(name); + defer free(name); + + for (true) match (xml::scan(parser)?) { + case let start: xml::elementstart => + ignore(parser, start)?; + case let end: xml::elementend => + assert(end == name || end == ""); + break; + case void => + return invalid; + case => yield; + }; +}; diff --git a/gir/types.ha b/gir/types.ha new file mode 100644 index 0000000..6616d69 --- /dev/null +++ b/gir/types.ha @@ -0,0 +1,363 @@ +export type repository = struct { + version: str, + + includes: []include, + namespaces: []namespace, +}; +export fn repository_finish(repo: *repository) void = { + free(repo.version); + for (let i = 0z; i < len(repo.includes); i += 1) { + include_finish(&repo.includes[i]); + }; + for (let i = 0z; i < len(repo.namespaces); i += 1) { + namespace_finish(&repo.namespaces[i]); + }; +}; + +export type include = struct { + name: str, + version: str, +}; +export fn include_finish(incl: *include) void = { + free(incl.name); + free(incl.version); +}; + +export type namespace = struct { + name: str, + version: str, + + aliases: []alias, + classes: []class, + interfaces: []interface, + records: []record, + enums: []enumeration, + functions: []function, + unions: []union_, + bitfields: []bitfield, + callbacks: []callback, + constants: []constant, +}; +export fn namespace_finish(ns: *namespace) void = { + free(ns.name); + free(ns.version); + // TODO +}; + +export type alias = struct { + documentation, + + name: str, + c_type: str, + + inner: simple_type, +}; + +export type class = struct { + documentation, + + name: str, + glib_type_name: str, + glib_get_type: str, + parent: str, + glib_ref_func: str, + glib_unref_func: str, + glib_set_value_func: str, + glib_get_value_func: str, + c_type: str, + symbol_prefix: str, + abstract: bool, + fundamental: bool, + final: bool, + + entries: []entry, + implements: []str, + constructors: []constructor, + methods: []method, + functions: []function, + virtual_methods: []virtual_method, + properties: []property, + signals: []signal, + constants: []constant, + callbacks: []callback, +}; +export type interface = struct { + documentation, + + name: str, + glib_type_name: str, + glib_get_type: str, + symbol_prefix: str, + c_type: str, + + entries: []entry, + prerequisites: []str, + implements: []str, + functions: []function, + constructors: []constructor, + methods: []method, + virtual_methods: []virtual_method, + properties: []property, + signals: []signal, + callbacks: []callback, + constants: []constant, +}; +export type record = struct { + documentation, + + name: str, + c_type: str, + disguised: bool, + opaque: bool, + pointer: bool, + glib_type_name: str, + glib_get_type: str, + symbol_prefix: str, + foreign: bool, + copy_function: str, + free_function: str, + + entries: []entry, + functions: []function, + methods: []method, + constructors: []constructor, +}; + +// FIXME: find a better name for this +export type entry = (field | union_ | record); + +export type constructor = struct { + callable, + + params: []callable_param, + return_value: (callable_return | void), +}; +export type method = struct { + callable, + + glib_set_property: str, + glib_get_property: str, + + instance: instance_parameter, + params: []callable_param, + return_value: (callable_return | void), +}; +export type virtual_method = struct { + callable, + + invoker: str, + + instance: instance_parameter, + params: []callable_param, + return_value: (callable_return | void), +}; +export type field = struct { + documentation, + + name: str, + writable: bool, + readable: bool, + private: bool, + bits: uint, + + type_: (callback | any_type), +}; +export type property = struct { + documentation, + + name: str, + writable: bool, + readable: bool, + construct: bool, + construct_only: bool, + setter: str, + getter: str, + default_value: str, + transfer_ownership: str, + + type_: any_type, +}; + +export type enumeration = struct { + documentation, + + name: str, + c_type: str, + glib_type_name: str, + glib_get_type: str, + glib_error_domain: str, + + members: []member, + functions: []function, +}; + +export type function = struct { + callable, + + params: []callable_param, + return_value: (callable_return | void), +}; + +export type union_ = struct { + documentation, + + name: str, + c_type: str, + symbol_prefix: str, + glib_type_name: str, + glib_get_type: str, + copy_function: str, + free_function: str, + + entries: []entry, + constructors: []constructor, + methods: []method, + functions: []function, +}; + +export type bitfield = struct { + documentation, + + name: str, + c_type: str, + glib_type_name: str, + glib_get_type: str, + + members: []member, + functions: []function, +}; + +export type callback = struct { + documentation, + + name: str, + c_type: str, + throws: bool, + + params: []callable_param, + return_value: (callable_return | void), +}; + +export type constant = struct { + documentation, + + name: str, + value: str, + c_type: str, + c_identifier: str, + + type_: (any_type | void), +}; + +export type signal = struct { + documentation, + + name: str, + detailed: bool, + when: str, + action: bool, + no_hooks: bool, + no_recurse: bool, + emitter: str, + + params: []callable_param, + return_value: (callable_return | void), +}; + +export type member = struct { + documentation, + + name: str, + value: str, + c_identifier: str, + glib_nick: str, + glib_name: str, +}; + +export type callable = struct { + documentation, + + name: str, + c_identifier: str, + shadowed_by: str, + shadows: str, + throws: bool, +}; + +export type callable_param = struct { + documentation, + + name: str, + is_nullable: bool, + allow_none: bool, + introspectable: bool, + closure: uint, + destroy: uint, + scope: str, + direction: str, + caller_allocates: bool, + optional: bool, + skip: bool, + transfer_ownership: str, + + parameter: (any_type | varargs), +}; + +export type instance_parameter = struct { + documentation, + + name: str, + is_nullable: bool, + allow_none: bool, + direction: str, + caller_allocates: str, + transfer_ownership: str, + + type_: simple_type, +}; + +export type varargs = void; + +export type callable_return = struct { + documentation, + + introspectable: bool, + is_nullable: bool, + closure: uint, + scope: str, + destroy: uint, + skip: bool, + allow_none: bool, + transfer_ownership: str, + + type_: any_type, +}; + +export type documentation = struct { + doc: str, +}; + +export type simple_type = struct { + documentation, + + name: str, + c_type: str, + introspectable: bool, + + inner: []any_type, +}; + +export type array_type = struct { + documentation, + + name: str, + zero_terminated: bool, + fixed_size: uint, + introspectable: bool, + length: uint, + c_type: str, + + inner: *any_type, +}; + +export type any_type = (simple_type | array_type); |