amiga-sys 0.0.2

Rust FFI bindings for the Amiga (m68k) system libraries
# converts #define constants which rust-bindgen has missed

import sys
import re
import os

# header files matching these are ignored
ignored_header_filenames = [
    "dosasl.h",
    "dosextens.h",
    "ansiio.h",
    "libraries.h",
    "ports.h",
    "semaphores.h",
    "mathffp.h",
    "mathieeedp.h",
    "mathieeesp.h",
    "memory.h",
    "copper.h",
    "gels.h",
    "reaction_macros.h",
    "reaction_prefs.h",
]

# names matching these strings are ignored
ignored_names = [
    "FOREVER", "NOT",
    "INCLUDE_VERSION", "LIBRARY_MINIMUM",
    "GLOBAL", "IMPORT", "STATIC", "REGISTER", "VOID", "CONST",
    "BBID_DOS", "BBID_KICK",
    "GetColorEnd", "GetFileEnd", "GetFontEnd", "GetScreenModeEnd", "EndDateBrowser", "End",
    "PI", "TWO_PI", "PI2", "PI4", "E", "LOG10", "FPTEN", "FPONE", "FPHALF", "FPZERO",
    "LCLABEL_NOLABEL",
    "StartHLayout", "StartVLayout", "VCentered", "TAligned", "BAligned", "HCentered", "LAligned",
    "RAligned", "EvenSized",
    "ie_X", "ie_Y", "ie_EventAddress", "ie_Prev1DownCode", "ie_Prev1DownQual", "ie_Prev2DownCode",
    "ie_Prev2DownQual", "du_Flags", "lh_Node", "pd_PIOR0", "pd_SIOR0", "pd_PIOR1", "pd_SIOR1",
    "dfh_TagList",
    "PRT_STDARGS",
    "lbs_Reverse",
    "ChunkyToPlanarPtr",
    "tf_Extension",
    "opAddMember",
    "rf_File", "rf_Dir", "rf_LeftEdge", "rf_TopEdge", "rf_Width", "rf_Height", "rf_NumArgs",
    "rf_ArgList", "rf_UserData", "rf_Pat",
    "NM_BARLABEL",
    "TAG_USER", "io_HighOffset"
]

# values starting with these strings are ignored
ignored_values = [
    "NewObject(",
    "sizeof(",
    "(sizeof",
    "((struct",
    "custom.dmacon",
    "custom.intena",
    "((void)(custom.dmacon",
    "((void)(custom.intena",
]

# main

if len(sys.argv) < 3:
    print(f"Usage: {sys.argv[0]} bindings.rs headerfile1.h [headerfile2.h] ...")
    exit(1)

# "pub const" definitions in bindings.rs
binding_consts = {}

# collect bindgen generated type definitions so that they are not repeated
bfilename = sys.argv[1]
with open(bfilename, 'r') as file:
    for line_number, line in enumerate(file):
        if line.startswith("pub const "):
            name = line.replace("pub const ", "").split(":")[0]
            binding_consts[name] = { "line": line, "line_number": line_number }

# print helpers
print("/* automatically generated by the amiga-sys tools */\n")
print("use crate::*;\n")
print("const fn MAKE_ID(c1: char, c2: char, c3: char, c4: char) -> u32 {")
print("    ((c1 as u32) << 24) | ((c2 as u32) << 16) | ((c3 as u32) << 8) | (c4 as u32)")
print("}")
print("const fn MAKE_SID(c1: char, c2: char, c3: char, c4: char) -> u32 {")
print("    ((c1 as u32) << 24) | ((c2 as u32) << 16) | ((c3 as u32) << 8) | (c4 as u32)")
print("}")
print("pub const TAG_USER: u32 = 1 << 31;")
print("pub const BBID_DOS: [u8; 4] = [ b'D', b'O', b'S', 0 ];")
print("pub const BBID_KICK: [u8; 4] = [ b'K', b'I', b'C', b'K' ];")

# process header files
used_names = []
for hfilename in sys.argv[2:]:
    short_hfilename = os.path.basename(hfilename)
    if short_hfilename in ignored_header_filenames:
        continue
    with open(hfilename, 'r', encoding='iso-8859-1') as file:
        for line_number, line in enumerate(file):
            line = line.strip()
            if not line.startswith("#define"):
                continue
            def_name_value = line.replace("#define", "").replace("\t", " ").replace("\n", "")
            # remove comments
            def_name_value = def_name_value.split("/*")[0].split("//")[0]
            # get name and value
            parts = def_name_value.strip().split(" ", 1)
            if len(parts) < 2:
                continue
            name = parts[0].strip()
            value = parts[1].strip()
            # macro "functions" are not supported
            if "(" in name:
                continue
            # check ignored items
            for iv in ignored_values:
                if value.startswith(iv):
                    name = "VOID" # this makes this #define skipped
            if name in ignored_names:
                continue
            if name in binding_consts or name in used_names:
                continue
            # remove unnecessary ()
            if value.startswith("(") and value.endswith(")"):
                value = value[1:-1]
            # fix numbers marked as long "123L"
            if re.findall(r'[0-9]L$', value):
                value = value[:-1]
            if re.findall(r'[0-9]L;$', value):
                value = value[:-2]
            # fix ctrl_f
            if value == "(long)1<<SIGBREAKB_CTRL_F":
                value = value[6:]
            # print it!
            print(f"pub const {name}: u32 = {value};")
            used_names.append(name)