WerWolv / PatternLanguage

The Pattern Language used by the ImHex Hex Editor
https://imhex.werwolv.net
GNU Lesser General Public License v2.1
173 stars 44 forks source link

[Enhancement] Allow attributes, like format_read, to have inlined code #116

Open BobSmun opened 2 months ago

BobSmun commented 2 months ago

Currently, various attributes like format_read require a string containing a function name as the argument. A lot of the time, these functions seem to end up being a simple one-liner.

// == current ==
struct DATE
{
    u16 year;
    u8 month;
    u8 day;
}
[[
    format_read("read_DATE"),
    transform("transform_DATE"),
    ...
]];
fn read_DATE(auto v)
{
    return std::format("{}-{}-{}", v.year, v.month, v.day);
};
fn transform_DATE(auto v)
{
    return (v.year * 10000) + (v.month * 100) + v.day;
};

This adds a decent amount of boilerplate overhead, making the underlying data format specification a little more difficult to read (due to tool integration 'noise'). Although the function could be moved to a different part of the pattern file, to somewhat separate data format from integration, the function would then no longer be co-located with the data it's operating on.

I feel things could be cleaner / clearer, if the contents of that function were optionally able to be inlined. This also avoids needing the pattern creator to make sure the function is uniquely named, as well as having to access the data through a (named) parameter.

As a first pass suggestion, inlining simple one-liners could look like:

// == proposed ==
struct DATE
{
    u16 year;
    u8 month;
    u8 day;
}
[[
    format_read(std::format("{}-{}-{}", year, month, day)),
    transform((year * 10000) + (month * 100) + day),
    ...
]];
BobSmun commented 2 months ago

Multi-line functions might also be able to be supported, if we consider the concept of lambdas and borrowing some of its formatting. We might go from something that looks like:

// == current (with a more complicated function example) ==
struct DATE
{
    u16 year;
    u8 month;
    u8 day;
}
[[
    format_read("read_DATE"),
    transform("transform_DATE"),
    format_write("write_DATE"),
    ...
]];
fn read_DATE(auto v)
{
    return std::format("{}-{}-{}", v.year, v.month, v.day);
};
fn transform_DATE(auto v)
{
    return (v.year * 10000) + (v.month * 100) + v.day;
};
fn write_DATE(str v)
{
    //not many examples of write functions, let's assume...
    u64 parsed = std::string::parse_int(v, 0);

    DATE result;
    result.year = parsed / 10000;
    result.month = (parsed / 100) % 100;
    result.day = parsed % 100;
    return result;
};

to something perhaps like:

// == proposed (with a more complicated function example) ==
struct DATE
{
    u16 year;
    u8 month;
    u8 day;
}
[[
    format_read(std::format("{}-{}-{}", year, month, day)),
    transform((year * 10000) + (month * 100) + day),
    format_write([](str v)
        {
            //not many examples of write functions, let's assume...
            u64 parsed = std::string::parse_int(v, 0);

            DATE result;
            result.year = parsed / 10000;
            result.month = (parsed / 100) % 100;
            result.day = parsed % 100;
            return result;
        }),
    ...
]];