diff options
author | bfredl <bjorn.linse@gmail.com> | 2023-04-03 15:21:24 +0200 |
---|---|---|
committer | bfredl <bjorn.linse@gmail.com> | 2023-04-07 21:30:21 +0200 |
commit | efb0896f21e03f64e3a14e7c09994e81956f47b9 (patch) | |
tree | f88cca66495b34631d037bffc3cda6a8db1329fe /src/nvim/generators/gen_api_dispatch.lua | |
parent | 04933b1ea968f958d2541dd65fd33ebb503caac3 (diff) | |
download | rneovim-efb0896f21e03f64e3a14e7c09994e81956f47b9.tar.gz rneovim-efb0896f21e03f64e3a14e7c09994e81956f47b9.tar.bz2 rneovim-efb0896f21e03f64e3a14e7c09994e81956f47b9.zip |
refactor(api): make typed dicts appear as types in the source code
problem: can we have Serde?
solution: we have Serde at home
This by itself is just a change of notation, that could be quickly
merged to avoid messy merge conflicts, but upcoming changes are planned:
- keysets no longer need to be defined in one single file. `keysets.h` is
just the initial automatic conversion of the previous `keysets.lua`.
keysets just used in a single api/{scope}.h can be moved to that file, later on.
- Typed dicts will have more specific types than Object. this will
enable most of the existing manual typechecking boilerplate to be eliminated.
We will need some annotation for missing value, i e a boolean will
need to be represented as a TriState (none/false/true) in some cases.
- Eventually: optional parameters in form of a `Dict opts` final
parameter will get added in some form to metadata. this will require
a discussion/desicion about type forward compatibility.
Diffstat (limited to 'src/nvim/generators/gen_api_dispatch.lua')
-rw-r--r-- | src/nvim/generators/gen_api_dispatch.lua | 140 |
1 files changed, 106 insertions, 34 deletions
diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index 8aa1829364..f292c265ec 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -8,6 +8,7 @@ if arg[1] == '--help' then print(' 3: functions metadata output file (funcs_metadata.generated.h)') print(' 4: API metadata output file (api_metadata.mpack)') print(' 5: lua C bindings output file (lua_api_c_bindings.generated.c)') + print(' 6: keyset definitions output file (keysets_defs.generated.h)') print(' rest: C files where API functions are defined') end assert(#arg >= 4) @@ -32,6 +33,7 @@ local funcs_metadata_outputf = arg[3] -- output metadata mpack file, for use by other build scripts local mpack_outputf = arg[4] local lua_c_bindings_outputf = arg[5] +local keysets_outputf = arg[6] -- set of function names, used to detect duplicates local function_names = {} @@ -42,8 +44,57 @@ local function startswith(String,Start) return string.sub(String,1,string.len(Start))==Start end +local function add_function(fn) + local public = startswith(fn.name, "nvim_") or fn.deprecated_since + if public and not fn.noexport then + functions[#functions + 1] = fn + function_names[fn.name] = true + if #fn.parameters >= 2 and fn.parameters[2][1] == 'Array' and fn.parameters[2][2] == 'uidata' then + -- function receives the "args" as a parameter + fn.receives_array_args = true + -- remove the args parameter + table.remove(fn.parameters, 2) + end + if #fn.parameters ~= 0 and fn.parameters[1][2] == 'channel_id' then + -- this function should receive the channel id + fn.receives_channel_id = true + -- remove the parameter since it won't be passed by the api client + table.remove(fn.parameters, 1) + end + if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'error' then + -- function can fail if the last parameter type is 'Error' + fn.can_fail = true + -- remove the error parameter, msgpack has it's own special field + -- for specifying errors + fn.parameters[#fn.parameters] = nil + end + if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'arena' then + -- return value is allocated in an arena + fn.arena_return = true + fn.parameters[#fn.parameters] = nil + end + if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'lstate' then + fn.has_lua_imp = true + fn.parameters[#fn.parameters] = nil + end + end +end + +local keysets = {} + +local function add_keyset(val) + local keys = {} + for _,field in ipairs(val.fields) do + if field.type ~= 'Object' then + error 'not yet implemented: types other than Object' + end + table.insert(keys, field.name) + end + table.insert(keysets, {val.keyset_name, keys}) +end + -- read each input file, parse and append to the api metadata -for i = 6, #arg do +for i = 7, #arg do local full_path = arg[i] local parts = {} for part in string.gmatch(full_path, '[^/]+') do @@ -55,39 +106,11 @@ for i = 6, #arg do local tmp = c_grammar.grammar:match(input:read('*all')) for j = 1, #tmp do - local fn = tmp[j] - local public = startswith(fn.name, "nvim_") or fn.deprecated_since - if public and not fn.noexport then - functions[#functions + 1] = tmp[j] - function_names[fn.name] = true - if #fn.parameters >= 2 and fn.parameters[2][1] == 'Array' and fn.parameters[2][2] == 'uidata' then - -- function receives the "args" as a parameter - fn.receives_array_args = true - -- remove the args parameter - table.remove(fn.parameters, 2) - end - if #fn.parameters ~= 0 and fn.parameters[1][2] == 'channel_id' then - -- this function should receive the channel id - fn.receives_channel_id = true - -- remove the parameter since it won't be passed by the api client - table.remove(fn.parameters, 1) - end - if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'error' then - -- function can fail if the last parameter type is 'Error' - fn.can_fail = true - -- remove the error parameter, msgpack has it's own special field - -- for specifying errors - fn.parameters[#fn.parameters] = nil - end - if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'arena' then - -- return value is allocated in an arena - fn.arena_return = true - fn.parameters[#fn.parameters] = nil - end - if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'lstate' then - fn.has_lua_imp = true - fn.parameters[#fn.parameters] = nil - end + local val = tmp[j] + if val.keyset_name then + add_keyset(val) + else + add_function(val) end end input:close() @@ -195,6 +218,7 @@ funcs_metadata_output:close() -- start building the dispatch wrapper output local output = io.open(dispatch_outputf, 'wb') +local keysets_defs = io.open(keysets_outputf, 'wb') -- =========================================================================== -- NEW API FILES MUST GO HERE. @@ -224,6 +248,52 @@ output:write([[ ]]) +for _,keyset in ipairs(keysets) do + local name, keys = unpack(keyset) + local special = {} + local function sanitize(key) + if special[key] then + return key .. "_" + end + return key + end + + for i = 1,#keys do + if vim.endswith(keys[i], "_") then + keys[i] = string.sub(keys[i],1, #(keys[i]) - 1) + special[keys[i]] = true + end + end + local neworder, hashfun = hashy.hashy_hash(name, keys, function (idx) + return name.."_table["..idx.."].str" + end) + + keysets_defs:write("extern KeySetLink "..name.."_table[];\n") + + output:write("KeySetLink "..name.."_table[] = {\n") + for _, key in ipairs(neworder) do + output:write(' {"'..key..'", offsetof(KeyDict_'..name..", "..sanitize(key)..")},\n") + end + output:write(' {NULL, 0},\n') + output:write("};\n\n") + + output:write(hashfun) + + output:write([[ +Object *KeyDict_]]..name..[[_get_field(void *retval, const char *str, size_t len) +{ + int hash = ]]..name..[[_hash(str, len); + if (hash == -1) { + return NULL; + } + + return (Object *)((char *)retval + ]]..name..[[_table[hash].ptr_off); +} + +]]) + keysets_defs:write("#define api_free_keydict_"..name.."(x) api_free_keydict(x, "..name.."_table)\n") +end + local function real_type(type) local rv = type local rmatch = string.match(type, "Dict%(([_%w]+)%)") @@ -475,6 +545,7 @@ output:write([[ #include "nvim/func_attr.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/api/private/dispatch.h" #include "nvim/lua/converter.h" #include "nvim/lua/executor.h" #include "nvim/memory.h" @@ -670,3 +741,4 @@ output:write([[ ]]) output:close() +keysets_defs:close() |