lbfgsbrs 0.1.2

Rust port of L-BFGS-B-C
Documentation
import subprocess
import os
import re
import argparse

LBFGSB_C_FILES = [
    "driver1.rs",
    "driver2.rs",
    "driver3.rs",
    "linesearch.rs",
    "miniCBLAS.rs",
    "subalgorithms.rs",
    "lbfgsb.rs",
    "linpack.rs",
    "print.rs",
    "timer.rs"
]


p_sizeof = re.compile(r'(.*)(std::mem::size_of::)(.*)(as u64)(.*)')

def massage_line(line):
    line = line.rstrip()

    # Remove various compile-time directives
    if line == "#![register_tool(c2rust)]":
        return ""
    if line == "use core::arch::asm;":
        return ""
    if line.startswith("#![feature("):
        return ""
    if line.startswith("#![allow("):
        return ""

    # Convert types
    line = line.replace("std::os::raw::c_int", "i32")
    line = line.replace("std::os::raw::c_ulonglong", "u64")
    line = line.replace("std::os::raw::c_longlong", "i64")
    line = line.replace("std::os::raw::c_uint", "u32")
    line = line.replace("std::os::raw::c_char", "u8")
    line = line.replace("std::os::raw::c_uchar", "u8")
    line = line.replace("std::os::raw::c_schar", "i8")
    line = line.replace("std::os::raw::c_void", "u8")
    line = line.replace("::std::mem::transmute", "core::mem::transmute")
    line = line.replace("libc::c_char", "core::ffi::c_char")
    line = line.replace("libc::c_schar", "core::ffi::c_schar")
    line = line.replace("libc::c_uchar", "core::ffi::c_uchar")
    line = line.replace("libc::c_int", "core::ffi::c_int")
    line = line.replace("libc::c_uint", "core::ffi::c_uint")
    line = line.replace("libc::c_double", "f64")
    line = line.replace("libc::c_ulonglong", "u64")
    line = line.replace("libc::c_longlong", "i64")
    line = line.replace("libc::c_ulong", "u32") # this must come after the longlong
    line = line.replace("libc::c_long", "i32")
    line = line.replace("libc::c_void", "core::ffi::c_void")

    line = line.replace("::std::mem::size_of", "core::mem::size_of")
    line = line.replace("::std::vec::", "alloc::vec::")
    line = line.replace(": Vec::", ": alloc::vec::Vec::")
    
    line = line.replace("use std::arch::asm;", "")
    if p_sizeof.search(line):
        line = p_sizeof.sub(r'\g<1>\g<2>\g<3>as u32\g<5>', line)

    # Replace this ASM weirdness with a barrier
    compiler_fence = (
        "core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);"
    )
    line = line.replace(
        'asm!("", inlateout(reg) a, options(preserves_flags, pure, readonly));',
        compiler_fence,
    )

    return line

def lint():
    # lint the c2rust using cargo and a cleanup pass
    build = subprocess.run(
        ["rustup", "run", "nightly", "cargo", "build", "--target=aarch64-apple-darwin"],
        stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    state = "SEARCHING"
    subs = {}
    warntype = ""
    token = ""
    p_token = re.compile(r'(.*)`(.*)`(.*)')
    p_line = re.compile(r'(.*)--> (.*):([0-9]*):([0-9]*)')
    for line in build.stdout.decode('utf8').split('\n'):
        if line.startswith("warning:"):
            if "value assigned to" in line and "is never read":
                warntype = "unused init"
                token = p_token.search(line).group(2)
                state = "FOUND"
            elif "unused variable" in line:
                warntype = "unused variable"
                token = p_token.search(line).group(2)
                state = "FOUND"
            elif "variable does not need to be mutable" in line:
                warntype = "remove mut"
                token = 'mut'
                state = "FOUND"
            elif "function" in line and "is never used in line":
                warntype = "unused func"
                token = p_token.search(line).group(2)
                state = "FOUND"
            else:
                state = "SEARCHING"
                pass
        if state == "FOUND":
            p = p_line.search(line)
            if p:
                fname = p.group(2)
                fline = int(p.group(3))
                fcol = int(p.group(4))
                if fname in subs:
                    subs[fname][fline] = [warntype, token, fcol]
                else:
                    subs[fname] = {fline: [warntype, token, fcol]}
                state = "SEARCHING"
                warntype = ""

    for fname in subs.keys():
        if 'src' in fname:
            # print(fname)
            # print(subs[fname])
            with open(fname, "r") as src_file:
                sfile = src_file.readlines()
            with open(fname, "w") as dst_file:
                line_no = 1
                for line in sfile:
                    if line_no in subs[fname]:
                        warn = subs[fname][line_no]
                        if "unused init" in warn[0]:
                            if " = 0" in line:
                                # this is an unused 0-init
                                line = line.replace(" = 0", "")
                            else:
                                # this is an unused assignment
                                line = line[:warn[2] - 1] + 'let _' + line[warn[2] - 1:]
                        elif "unused variable" in warn[0]:
                            line = line[:warn[2]-1] + '_' + line[warn[2]-1:]
                            # print("DEBUG: {}".format(subs[fname][line_no]))
                        elif "remove mut":
                            # print("DEBUG: {}".format(line))
                            line = line[:warn[2]-1] + line[warn[2]+3:]
                        elif "unused func":
                            line = line[:warn[2]-1] + '_' + line[warn[2]-1:]
                        else:
                            print("TODO: {}".format(subs[fname][line_no]))
                    line_no += 1
                    print(line, file=dst_file, end="")

def process_files(source_directory):
    for file in LBFGSB_C_FILES:
        rs_file = source_directory + "/" + file
        mod_name = file.split(".")[0]

        print(f"    pub mod {mod_name};")
        with open(rs_file, "r") as src_file:
            with open(f"{mod_name}.rs", "w") as dest_file:
                #print("use core::ffi::*;", file=dest_file)
                for line in src_file:
                    print(massage_line(line), file=dest_file)

def main():
    parser = argparse.ArgumentParser(description="A simple script to lint and/or massage files in a source directory.")
    
    # Define command line arguments
    parser.add_argument('-l', '--lint', action='store_true', help="Flag to lint the files.")
    parser.add_argument('-d', '--source_directory', type=str, required=True, help="The source directory to lint.")
    parser.add_argument('-m', '--massage_line', action='store_true', help="Flag to massage each line of the files.")

    # Parse command line arguments
    args = parser.parse_args()

    if args.lint:
        lint()
    
    # Process the files and massage lines if the flag is set
    if args.massage_line:
        process_files(args.source_directory)
    elif not args.lint:
        print("No action specified. Use -l for linting or -m for massage.")

if __name__ == "__main__":
    main()